From 04fc5bf16c15b71fe24b77af12ee213e43387f27 Mon Sep 17 00:00:00 2001 From: btxgear Date: Mon, 4 Mar 2024 09:16:53 +0100 Subject: [PATCH 1/3] fix issues + starting frontendInteractions --- daikoku/app/daikoku.scala | 21 +- daikoku/app/domain/entities.scala | 7 + daikoku/app/domain/json.scala | 18 ++ daikoku/app/env/env.scala | 12 +- daikoku/app/jobs/AnonymousReportingJob.scala | 220 ++++++++++++++++++ daikoku/app/storage/api.scala | 3 + .../drivers/postgres/PostgresDataStore.scala | 14 +- daikoku/conf/base.conf | 8 + daikoku/javascript/src/apps/DaikokuApp.tsx | 11 + .../anonymousreporting/AnonymousReporting.tsx | 31 +++ .../javascript/src/contexts/navContext.tsx | 5 + 11 files changed, 332 insertions(+), 18 deletions(-) create mode 100644 daikoku/app/jobs/AnonymousReportingJob.scala create mode 100644 daikoku/javascript/src/components/adminbackoffice/anonymousreporting/AnonymousReporting.tsx diff --git a/daikoku/app/daikoku.scala b/daikoku/app/daikoku.scala index c95be272..3544b9a3 100644 --- a/daikoku/app/daikoku.scala +++ b/daikoku/app/daikoku.scala @@ -4,30 +4,19 @@ import org.apache.pekko.http.scaladsl.util.FastFuture import org.apache.pekko.stream.Materializer import com.softwaremill.macwire._ import controllers.{Assets, AssetsComponents} -import fr.maif.otoroshi.daikoku.actions.{ - DaikokuAction, - DaikokuActionMaybeWithGuest, - DaikokuActionMaybeWithoutUser, - DaikokuTenantAction -} +import fr.maif.otoroshi.daikoku.actions.{DaikokuAction, DaikokuActionMaybeWithGuest, DaikokuActionMaybeWithoutUser, DaikokuTenantAction} import fr.maif.otoroshi.daikoku.ctrls._ import fr.maif.otoroshi.daikoku.env._ import fr.maif.otoroshi.daikoku.modules.DaikokuComponentsInstances import fr.maif.otoroshi.daikoku.utils.RequestImplicits._ import fr.maif.otoroshi.daikoku.utils.admin._ -import fr.maif.otoroshi.daikoku.utils.{ - ApiService, - DeletionService, - Errors, - OtoroshiClient, - Translator -} +import fr.maif.otoroshi.daikoku.utils.{ApiService, DeletionService, Errors, OtoroshiClient, Translator} import io.vertx.core.Vertx import io.vertx.core.buffer.Buffer import io.vertx.core.net.{PemKeyCertOptions, PemTrustOptions} import io.vertx.pgclient.{PgConnectOptions, PgPool, SslMode} import io.vertx.sqlclient.PoolOptions -import jobs.{ApiKeyStatsJob, AuditTrailPurgeJob, QueueJob, OtoroshiVerifierJob} +import jobs.{AnonymousReportingJob, ApiKeyStatsJob, AuditTrailPurgeJob, OtoroshiVerifierJob, QueueJob} import play.api.ApplicationLoader.Context import play.api._ import play.api.http.{DefaultHttpFilters, HttpErrorHandler} @@ -65,6 +54,7 @@ package object modules { lazy val deletor = wire[QueueJob] lazy val statsJob = wire[ApiKeyStatsJob] lazy val auditTrailPurgeJob = wire[AuditTrailPurgeJob] + lazy val anonReportingJob = wire[AnonymousReportingJob] lazy val otoroshiClient = wire[OtoroshiClient] lazy val paymentClient = wire[PaymentClient] @@ -237,7 +227,7 @@ package object modules { deletor.start() verifier.start() auditTrailPurgeJob.start() - + anonReportingJob.start() env.onStartup() applicationLifecycle.addStopHook { () => @@ -245,6 +235,7 @@ package object modules { verifier.stop() statsJob.stop() auditTrailPurgeJob.stop() + anonReportingJob.stop() env.onShutdown() pgPool.close() FastFuture.successful(()) diff --git a/daikoku/app/domain/entities.scala b/daikoku/app/domain/entities.scala index 45329330..b8a4b47e 100644 --- a/daikoku/app/domain/entities.scala +++ b/daikoku/app/domain/entities.scala @@ -151,3 +151,10 @@ case class Evolution( ) extends CanJson[Evolution] { override def asJson: JsValue = json.EvolutionFormat.writes(this) } + +case class ReportsInfo( + id: DatastoreId, + activated: Boolean + ) extends CanJson[StepValidator] { + override def asJson: JsValue = json.ReportsInfoFormat.writes(this) +} \ No newline at end of file diff --git a/daikoku/app/domain/json.scala b/daikoku/app/domain/json.scala index aa4c3379..7cc2f103 100644 --- a/daikoku/app/domain/json.scala +++ b/daikoku/app/domain/json.scala @@ -4329,6 +4329,24 @@ object json { ) } + val ReportsInfoFormat: Format[ReportsInfo] = + new Format[ReportsInfo] { + override def reads(json: JsValue): JsResult[ReportsInfo] = + Try { + JsSuccess(ReportsInfo( + (json \ "_id").as(DatastoreIdFormat), + (json \ "activated").as[Boolean], + )) + } recover { + case e => JsError(e.getMessage) + } get + + override def writes(o: ReportsInfo): JsValue = + Json.obj( + "_id" -> DatastoreIdFormat.writes(o.id), + "activated" -> o.activated, + ) + } val SeqOtoroshiSettingsFormat = Format( Reads.seq(OtoroshiSettingsFormat), Writes.seq(OtoroshiSettingsFormat) diff --git a/daikoku/app/env/env.scala b/daikoku/app/env/env.scala index 1cbc5fea..2dbe5f08 100644 --- a/daikoku/app/env/env.scala +++ b/daikoku/app/env/env.scala @@ -10,7 +10,7 @@ import com.auth0.jwt.{JWT, JWTVerifier} import fr.maif.otoroshi.daikoku.audit.AuditActorSupervizer import fr.maif.otoroshi.daikoku.domain.TeamPermission.Administrator import fr.maif.otoroshi.daikoku.domain.UsagePlan.FreeWithoutQuotas -import fr.maif.otoroshi.daikoku.domain.{TeamApiKeyVisibility, Tenant} +import fr.maif.otoroshi.daikoku.domain.{DatastoreId, ReportsInfo, TeamApiKeyVisibility, Tenant} import fr.maif.otoroshi.daikoku.logger.AppLogger import fr.maif.otoroshi.daikoku.login.LoginFilter import fr.maif.otoroshi.daikoku.utils._ @@ -242,6 +242,10 @@ class Config(val underlying: Configuration) { lazy val init: InitConfig = InitConfig(underlying) lazy val adminApiConfig: AdminApiConfig = AdminApiConfig(underlying) + + lazy val anonymousReportingUrl: String = underlying.get[String]("daikoku.anonymous-reporting.url") + lazy val anonymousReportingTimeout: Int = underlying.get[Int]("daikoku.anonymous-reporting.timeout") + lazy val anonymousReportingEnabled: Boolean = underlying.get[Boolean]("daikoku.anonymous-reporting.enabled") } sealed trait Env { @@ -558,7 +562,11 @@ class DaikokuEnv( .filter(v => v) .take(1) .toMat(Sink.ignore)(Keep.right) - .run()(materializer) + .run()(materializer).map(_ => { + dataStore.reportsInfoRepo.count().map { + case 0 => dataStore.reportsInfoRepo.save(ReportsInfo(id = DatastoreId(IdGenerator.uuid), activated = false)) + } + }) } override def onShutdown(): Unit = { diff --git a/daikoku/app/jobs/AnonymousReportingJob.scala b/daikoku/app/jobs/AnonymousReportingJob.scala new file mode 100644 index 00000000..d662e7c2 --- /dev/null +++ b/daikoku/app/jobs/AnonymousReportingJob.scala @@ -0,0 +1,220 @@ +package jobs + +import daikoku.BuildInfo +import fr.maif.otoroshi.daikoku.domain.{ Tenant, TenantMode, json} +import fr.maif.otoroshi.daikoku.env.Env +import fr.maif.otoroshi.daikoku.utils.IdGenerator +import org.apache.pekko.Done +import org.apache.pekko.actor.Cancellable +import org.apache.pekko.http.scaladsl.util.FastFuture +import org.apache.pekko.stream.Materializer +import org.joda.time.DateTime +import play.api.Logger +import play.api.libs.json.{JsArray, JsNumber, JsObject, Json} +import play.api.libs.ws.WSRequest + +import java.util.concurrent.atomic.AtomicReference +import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.duration.DurationInt + +class AnonymousReportingJob(env: Env) { + private val logger = Logger("AnonymousReportingJob") + private val ref = new AtomicReference[Cancellable]() + + implicit val ec: ExecutionContext = env.defaultExecutionContext + implicit val ev: Env = env + implicit val mat: Materializer = env.defaultMaterializer + private case class Data( + daikoku_id: String, + account_creation: Int, + api_documentation_pages: Int, + api_issues: Int, + api_posts: Int, + api_subscription: Int, + apis: Int, + audit_events: Int, + cmspages: Int, + consumptions: Int, + email_verifications: Int, + evolutions: Int, + messages: Int, + notifications: Int, + operations: Int, + password_reset: Int, + step_validator: Int, + subscription_demands: Int, + teams: Int, + tenants: Seq[Tenant], + translations: Int, + usage_plans: Int, + user_sessions: Int, + users: Int, + timestamp: JsNumber, + timestampStr: String, + id: String, + daikokuVersion: String, + javaVersion: JsObject, + os: JsObject + ) + + private val wSRequest: WSRequest = env.wsClient.url(ev.config.anonymousReportingUrl) + private val enabled: Boolean = ev.config.anonymousReportingEnabled + private val dataStore = ev.dataStore + + def start(): Unit = { + logger.info("Anonymous Reporting started") + if (ref.get() == null) { + ref.set( + env.defaultActorSystem.scheduler + .scheduleAtFixedRate(1.seconds, 6.hours) { + () => sendDatas() + } + ) + } + } + + def stop(): Unit = { + Option(ref.get()).foreach(_.cancel()) + } + + private def getData = { + for { + daikoku_id <- dataStore.reportsInfoRepo.findAll().map(seq => seq.head.id) + + account_creation <- dataStore.accountCreationRepo.findAllNotDeleted() + api_documentation_pages <- dataStore.apiDocumentationPageRepo.forAllTenant().findAllNotDeleted() + api_issues <- dataStore.apiIssueRepo.forAllTenant().findAllNotDeleted() + api_posts <- dataStore.apiPostRepo.forAllTenant().findAllNotDeleted() + api_subscription <- dataStore.apiSubscriptionRepo.forAllTenant().findAllNotDeleted() + apis <- dataStore.apiRepo.forAllTenant().findAllNotDeleted() + audit_events <- dataStore.auditTrailRepo.forAllTenant().findAllNotDeleted() + cmspages <- dataStore.cmsRepo.forAllTenant().findAllNotDeleted() + consumptions <- dataStore.consumptionRepo.forAllTenant().findAllNotDeleted() + email_verifications <- dataStore.emailVerificationRepo.forAllTenant().findAllNotDeleted() + evolutions <- dataStore.evolutionRepo.findAllNotDeleted() + messages <- dataStore.messageRepo.forAllTenant().findAllNotDeleted() + notifications <- dataStore.notificationRepo.forAllTenant().findAllNotDeleted() + operations <- dataStore.operationRepo.forAllTenant().findAllNotDeleted() + password_reset <- dataStore.passwordResetRepo.findAllNotDeleted() + step_validator <- dataStore.stepValidatorRepo.forAllTenant().findAllNotDeleted() + subscription_demands <- dataStore.subscriptionDemandRepo.forAllTenant().findAllNotDeleted() + teams <- dataStore.teamRepo.forAllTenant().findAllNotDeleted() + tenants <- dataStore.tenantRepo.findAllNotDeleted() + translations <- dataStore.translationRepo.forAllTenant().findAllNotDeleted() + usage_plans <- dataStore.usagePlanRepo.forAllTenant().findAllNotDeleted() + user_sessions <- dataStore.userSessionRepo.findAllNotDeleted() + users <- dataStore.userRepo.findAllNotDeleted() + + timestamp = json.DateTimeFormat.writes(DateTime.now()) + timestamp_str = DateTime.now().toString() + id = IdGenerator.uuid + daikoku_version = BuildInfo.version + java_version = Json.obj("version" -> System.getProperty("java.version"), "vendor" -> System.getProperty("java.vendor")) + os = Json.obj("name" -> System.getProperty("os.name"), "arch" -> System.getProperty("os.arch"), "version" -> System.getProperty("os.version")) + } yield Data( + daikoku_id = daikoku_id.value, + id = id, + tenants = tenants, + + account_creation = account_creation.length, + api_documentation_pages = api_documentation_pages.length, + api_issues = api_issues.length, + api_posts = api_posts.length, + api_subscription = api_subscription.length, + apis = apis.length, + audit_events = audit_events.length, + cmspages = cmspages.length, + consumptions = consumptions.length, + email_verifications = email_verifications.length, + evolutions = evolutions.length, + messages = messages.length, + notifications = notifications.length, + operations = operations.length, + password_reset = password_reset.length, + step_validator = step_validator.length, + subscription_demands = subscription_demands.length, + teams = teams.length, + translations = translations.length, + usage_plans = usage_plans.length, + user_sessions = user_sessions.length, + users = users.length, + + timestamp = timestamp, + timestampStr = timestamp_str, + daikokuVersion = daikoku_version, + javaVersion = java_version, + os = os) + } + + + private def sendDatas(): Future[Done] = { + dataStore.reportsInfoRepo.findAll().map(seq => seq.head).map(seq => { + if(enabled && seq.activated) { + for { + data <- getData + post = Json.obj( + "daikoku_cluster_id" -> data.daikoku_id, + "@id" -> data.id, + "tenants" -> JsArray(data.tenants.map(tenant => { + val visibility = if (tenant.isPrivate) { + "private" + } else { + "public" + } + Json.obj( + "tenantMode" -> tenant.tenantMode.getOrElse(TenantMode.Default).name, + "payements" -> tenant.thirdPartyPaymentSettings.length, + "displayMode" -> tenant.display.name, + "visibility" -> visibility, + "AuthRole" -> tenant.authProvider.name + )})), + "entities" -> Json.obj( + "account_creation" -> data.account_creation, + "api_documentation_pages" -> data.api_documentation_pages, + "api_issues" -> data.api_issues, + "api_posts" -> data.api_posts, + "api_subscription" -> data.api_subscription, + "apis" -> data.apis, + "audit_events" -> data.audit_events, + "cmspages" -> data.cmspages, + "consumptions" -> data.consumptions, + "email_verifications" -> data.email_verifications, + "evolutions" -> data.evolutions, + "messages" -> data.messages, + "notifications" -> data.notifications, + "operations" -> data.operations, + "password_reset" -> data.password_reset, + "step_validator" -> data.step_validator, + "subscription_demands" -> data.subscription_demands, + "teams" -> data.teams, + "translations" -> data.translations, + "usage_plans" -> data.usage_plans, + "user_sessions" -> data.user_sessions, + "users" -> data.users), + "features" -> Json.obj(), + "timestamp" -> data.timestamp, + "timestamp_str" -> data.timestampStr, + "daikoku_version" -> data.daikokuVersion, + "java_version" -> data.javaVersion, + "os" -> data.os, + ) + _ <- wSRequest.withRequestTimeout(ev.config.anonymousReportingTimeout.millis).post(post).map { resp => + if (resp.status != 200 && resp.status != 201 && resp.status != 204) { + logger.error(s"error while sending anonymous reports: ${resp.status} - ${resp.body}") + } else { + logger.info("Thank you for having anonymous reporting enabled, Data sent ! For more info see (url vers la doc)") //TODO Faire la doc et rjaouter l'URL + } + } + .recover { case e: Throwable => + logger.error("error while sending anonymous reports", e) + () + } + } yield Done + } else { + + logger.info("Anonymous reporting is disabled if you want to activate it for helping us, see (url vers la doc)") //TODO tout pareil + FastFuture.successful(Done) + } + }).flatten + } +} diff --git a/daikoku/app/storage/api.scala b/daikoku/app/storage/api.scala index 1533d7bc..bc1d06ee 100644 --- a/daikoku/app/storage/api.scala +++ b/daikoku/app/storage/api.scala @@ -387,6 +387,7 @@ trait TenantRepo extends Repo[Tenant, TenantId] trait UserRepo extends Repo[User, UserId] trait EvolutionRepo extends Repo[Evolution, DatastoreId] +trait ReportsInfoRepo extends Repo[ReportsInfo, DatastoreId] trait TeamRepo extends TenantCapableRepo[Team, TeamId] { def myTeams(tenant: Tenant, user: User)(implicit @@ -558,6 +559,8 @@ trait DataStore { def usagePlanRepo: UsagePlanRepo + def reportsInfoRepo: ReportsInfoRepo + def exportAsStream(pretty: Boolean, exportAuditTrail: Boolean = true)(implicit ec: ExecutionContext, mat: Materializer, diff --git a/daikoku/app/storage/drivers/postgres/PostgresDataStore.scala b/daikoku/app/storage/drivers/postgres/PostgresDataStore.scala index 9e7ac6c4..1e75f1a8 100644 --- a/daikoku/app/storage/drivers/postgres/PostgresDataStore.scala +++ b/daikoku/app/storage/drivers/postgres/PostgresDataStore.scala @@ -398,7 +398,8 @@ class PostgresDataStore(configuration: Configuration, env: Env, pgPool: PgPool) "operations" -> true, "subscription_demands" -> true, "step_validators" -> true, - "usage_plans" -> true + "usage_plans" -> true, + "reports_info" -> true ) private lazy val poolOptions: PoolOptions = new PoolOptions() @@ -543,6 +544,8 @@ class PostgresDataStore(configuration: Configuration, env: Env, pgPool: PgPool) ) private val _passwordResetRepo: PasswordResetRepo = new PostgresPasswordResetRepo(env, reactivePg) + private val _reportsInfoRepo: ReportsInfoRepo = + new PostgresReportsInfoRepo(env, reactivePg) private val _accountCreationRepo: AccountCreationRepo = new PostgresAccountCreationRepo(env, reactivePg) private val _translationRepo: TranslationRepo = @@ -618,6 +621,7 @@ class PostgresDataStore(configuration: Configuration, env: Env, pgPool: PgPool) override def consumptionRepo: ConsumptionRepo = _consumptionRepo override def passwordResetRepo: PasswordResetRepo = _passwordResetRepo + override def reportsInfoRepo: ReportsInfoRepo = _reportsInfoRepo override def accountCreationRepo: AccountCreationRepo = _accountCreationRepo @@ -911,7 +915,15 @@ class PostgresPasswordResetRepo(env: Env, reactivePg: ReactivePg) override def extractId(value: PasswordReset): String = value.id.value } +class PostgresReportsInfoRepo(env: Env, reactivePg: ReactivePg) + extends PostgresRepo[ReportsInfo, DatastoreId](env, reactivePg) + with ReportsInfoRepo { + override def tableName: String = "reports_info" + override def format: Format[ReportsInfo] = json.ReportsInfoFormat + + override def extractId(value: ReportsInfo): String = value.id.value +} class PostgresAccountCreationRepo(env: Env, reactivePg: ReactivePg) extends PostgresRepo[AccountCreation, DatastoreId](env, reactivePg) with AccountCreationRepo { diff --git a/daikoku/conf/base.conf b/daikoku/conf/base.conf index ed4047d3..fc140ee4 100644 --- a/daikoku/conf/base.conf +++ b/daikoku/conf/base.conf @@ -14,6 +14,14 @@ daikoku { storage = "postgres" storage = ${?DAIKOKU_STORAGE} + anonymous-reporting { + enabled = true + enabled = ${?DAIKOKU_ANONYMOUS_REPORTING_ENABLED} + url = "http://localhost:9000/daikoku/ingest" + timeout = 60000 + timeout = ${?DAIKOKU_ANONYMOUS_REPORTING_TIMEOUT} + } + postgres { host = "localhost" host = ${?DAIKOKU_POSTGRES_HOST} diff --git a/daikoku/javascript/src/apps/DaikokuApp.tsx b/daikoku/javascript/src/apps/DaikokuApp.tsx index 100368f4..26654e65 100644 --- a/daikoku/javascript/src/apps/DaikokuApp.tsx +++ b/daikoku/javascript/src/apps/DaikokuApp.tsx @@ -56,6 +56,7 @@ import { TenantAssets } from '../components/adminbackoffice/tenants/TenantAssets import { SessionModal } from '../contexts/modals/SessionModal'; import { ISession, IState, ITeamSimple, ITenant, IUserSimple } from '../types'; import {FastMode} from "../components/frontend/fastMode/FastMode"; +import {AnonymousReporting} from "../components/adminbackoffice/anonymousreporting/AnonymousReporting"; type DaikokuAppProps = { session: ISession, @@ -297,6 +298,16 @@ export const DaikokuApp = ({ } /> + + + + } + /> { + useDaikokuBackOffice(); + const { translate, Translation } = useContext(I18nContext); + const [isAnonEnabled, setIsAnonEnabled] = useState() + useEffect(() => { + + }, [isAnonEnabled]); + return ( + +
+
+

+ {translate('Anonymous reporting')} +

+
+
+ + +
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/daikoku/javascript/src/contexts/navContext.tsx b/daikoku/javascript/src/contexts/navContext.tsx index deb21147..39e23b10 100644 --- a/daikoku/javascript/src/contexts/navContext.tsx +++ b/daikoku/javascript/src/contexts/navContext.tsx @@ -896,6 +896,11 @@ export const useDaikokuBackOffice = (props?: { creation?: boolean }) => { action: () => navigateTo('import-export'), className: { active: currentTab === 'import-export' }, }, + anonymousreporting: { + label: translate('Anonymous Reporting'), + action: () => navigateTo('anonymous-reports'), + className: { active: currentTab === 'anonymous-reports' }, + }, }, }, } From e3aae8a4d57c1f9d3daa92eb6142af38b6beb9ec Mon Sep 17 00:00:00 2001 From: btxgear Date: Mon, 4 Mar 2024 15:24:09 +0100 Subject: [PATCH 2/3] added web integrations for enabling reporting --- .../app/controllers/AdminApiController.scala | 26 +++++++++- daikoku/app/domain/entities.scala | 7 +-- daikoku/app/domain/json.scala | 2 + daikoku/conf/base.conf | 2 +- daikoku/conf/routes | 2 + .../anonymousreporting/AnonymousReporting.tsx | 28 +++++++++-- .../src/components/frontend/team/MyHome.tsx | 50 +++++++++++++++++-- .../src/locales/en/translation.json | 7 ++- .../src/locales/fr/translation.json | 7 ++- daikoku/javascript/src/services/index.ts | 7 +++ daikoku/javascript/src/types/context.ts | 6 +++ 11 files changed, 129 insertions(+), 15 deletions(-) diff --git a/daikoku/app/controllers/AdminApiController.scala b/daikoku/app/controllers/AdminApiController.scala index eec92016..8a934530 100644 --- a/daikoku/app/controllers/AdminApiController.scala +++ b/daikoku/app/controllers/AdminApiController.scala @@ -18,7 +18,7 @@ import fr.maif.otoroshi.daikoku.utils.admin._ import io.vertx.pgclient.PgPool import org.apache.pekko.stream.scaladsl.Source import play.api.http.HttpEntity -import play.api.libs.json.{JsArray, JsObject, JsValue, Json} +import play.api.libs.json._ import play.api.libs.streams.Accumulator import play.api.mvc._ import storage.drivers.postgres.PostgresDataStore @@ -142,6 +142,30 @@ class StateController( } } } + def getAnonymousState: Action[AnyContent] = DaikokuAction.async { ctx => + DaikokuAdminOnly( + AuditTrailEvent(s"@{user.name} has accessed state of anonymous reporting") + )(ctx) { + env.dataStore.reportsInfoRepo.findAll() + .map(info => Ok(Json.obj("activated" -> info.head.activated, "id" -> info.head.id.value, "date" -> info.head.date, "message" -> "info fetched correctly")) + ) + } + } + + def updateAnonymousState(): Action[JsValue] = DaikokuAction.async(parse.json) { ctx => + DaikokuAdminOnly( + AuditTrailEvent(s"@{user.name} has set anonymous reporting to ${ctx.request.body}") + )(ctx) { + val body = ctx.request.body.as[JsObject] + for { + maybeDate <- env.dataStore.reportsInfoRepo.findAll().map(info => info.head.date) + _ <- env.dataStore.reportsInfoRepo.save(ReportsInfo(DatastoreId((body \ "id").as[String]), (body \ "value").as[Boolean], (body \ "currentDate").asOpt[Double] match { + case Some(value) => Some(value) + case None => maybeDate + })) + } yield (Ok(Json.obj("message" -> "anonymous reporting updated"))) + } + } private def removeAllUserSessions(ctx: DaikokuActionContext[AnyContent]) = { env.dataStore.userSessionRepo diff --git a/daikoku/app/domain/entities.scala b/daikoku/app/domain/entities.scala index b8a4b47e..8a37ea0d 100644 --- a/daikoku/app/domain/entities.scala +++ b/daikoku/app/domain/entities.scala @@ -153,8 +153,9 @@ case class Evolution( } case class ReportsInfo( - id: DatastoreId, - activated: Boolean - ) extends CanJson[StepValidator] { + id: DatastoreId, + activated: Boolean, + date: Option[Double] = None, + ) extends CanJson[ReportsInfo] { override def asJson: JsValue = json.ReportsInfoFormat.writes(this) } \ No newline at end of file diff --git a/daikoku/app/domain/json.scala b/daikoku/app/domain/json.scala index 7cc2f103..face2eec 100644 --- a/daikoku/app/domain/json.scala +++ b/daikoku/app/domain/json.scala @@ -4336,6 +4336,7 @@ object json { JsSuccess(ReportsInfo( (json \ "_id").as(DatastoreIdFormat), (json \ "activated").as[Boolean], + (json \ "date").asOpt[Double] )) } recover { case e => JsError(e.getMessage) @@ -4345,6 +4346,7 @@ object json { Json.obj( "_id" -> DatastoreIdFormat.writes(o.id), "activated" -> o.activated, + "date" -> o.date ) } val SeqOtoroshiSettingsFormat = Format( diff --git a/daikoku/conf/base.conf b/daikoku/conf/base.conf index fc140ee4..ef098ca3 100644 --- a/daikoku/conf/base.conf +++ b/daikoku/conf/base.conf @@ -17,7 +17,7 @@ daikoku { anonymous-reporting { enabled = true enabled = ${?DAIKOKU_ANONYMOUS_REPORTING_ENABLED} - url = "http://localhost:9000/daikoku/ingest" + url = "https://reporting.otoroshi.io/daikoku/ingest" timeout = 60000 timeout = ${?DAIKOKU_ANONYMOUS_REPORTING_TIMEOUT} } diff --git a/daikoku/conf/routes b/daikoku/conf/routes index ad3fb09a..4b8c9128 100644 --- a/daikoku/conf/routes +++ b/daikoku/conf/routes @@ -291,6 +291,8 @@ POST /api/state/migrate fr.maif.otoroshi.daikoku POST /api/state/lock fr.maif.otoroshi.daikoku.ctrls.StateController.enableMaintenanceMode() POST /api/state/unlock fr.maif.otoroshi.daikoku.ctrls.StateController.disableMaintenanceMode() GET /api/state/lock fr.maif.otoroshi.daikoku.ctrls.StateController.isMaintenanceMode() +GET /api/state/anonymous fr.maif.otoroshi.daikoku.ctrls.StateController.getAnonymousState() +POST /api/state/anonymous fr.maif.otoroshi.daikoku.ctrls.StateController.updateAnonymousState() POST /api/search fr.maif.otoroshi.daikoku.ctrls.GraphQLController.search() GET /api/render-schema fr.maif.otoroshi.daikoku.ctrls.GraphQLController.renderSchema() diff --git a/daikoku/javascript/src/components/adminbackoffice/anonymousreporting/AnonymousReporting.tsx b/daikoku/javascript/src/components/adminbackoffice/anonymousreporting/AnonymousReporting.tsx index 007bdf49..dce0488c 100644 --- a/daikoku/javascript/src/components/adminbackoffice/anonymousreporting/AnonymousReporting.tsx +++ b/daikoku/javascript/src/components/adminbackoffice/anonymousreporting/AnonymousReporting.tsx @@ -3,14 +3,34 @@ import { useDaikokuBackOffice } from '../../../contexts'; import {I18nContext} from "../../../contexts/i18n-context"; import {Can, daikoku, manage} from "../../utils"; import {BooleanInput} from "@maif/react-forms"; +import * as Services from '../../../services'; +import {toastr} from "react-redux-toastr"; export const AnonymousReporting = () => { useDaikokuBackOffice(); - const { translate, Translation } = useContext(I18nContext); - const [isAnonEnabled, setIsAnonEnabled] = useState() + const { translate } = useContext(I18nContext); + const [isAnonEnabled, setIsAnonEnabled] = useState(false) + const [daikokuId, setDaikokuId] = useState() useEffect(() => { + Services.getAnonymousState().then(res =>{ + setIsAnonEnabled(res.activated) + setDaikokuId(res.id) + }) + }, []); + + const changeValue = (value: boolean) => { + if (daikokuId) { + Services.updateAnonymousState(daikokuId, value).then(() => { + setIsAnonEnabled(value) + toastr.success(translate('Success'), translate(value ? "anonymous.reporting.success.enabled" : "anonymous.reporting.success.disabled" )) + }) + } else { + toastr.error(translate('Error'), translate("anonymous.reporting.error")) + setIsAnonEnabled(!value) + + } + } - }, [isAnonEnabled]); return (
@@ -21,7 +41,7 @@ export const AnonymousReporting = () => {
- +
diff --git a/daikoku/javascript/src/components/frontend/team/MyHome.tsx b/daikoku/javascript/src/components/frontend/team/MyHome.tsx index 16b6fe6a..7982e93b 100644 --- a/daikoku/javascript/src/components/frontend/team/MyHome.tsx +++ b/daikoku/javascript/src/components/frontend/team/MyHome.tsx @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/react-query'; -import { useContext } from 'react'; +import {useContext, useEffect, useState} from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useNavigate } from 'react-router-dom'; @@ -7,9 +7,17 @@ import { useNavigate } from 'react-router-dom'; import { I18nContext, updateTeam } from '../../../core'; import * as Services from '../../../services'; import { converter } from '../../../services/showdown'; -import { IApiWithAuthorization, isError, IState, ITenant, IUsagePlan, IUserSimple } from '../../../types'; +import { + IApiWithAuthorization, + isError, + IState, + ITenant, + IUserSimple +} from '../../../types'; import { ApiList } from './ApiList'; import { api as API, CanIDoAction, manage, Spinner } from '../../utils'; +import {toastr} from "react-redux-toastr"; +import {ModalContext} from "../../../contexts"; export const MyHome = () => { @@ -24,8 +32,40 @@ export const MyHome = () => { const navigate = useNavigate(); const { translate } = useContext(I18nContext); - - + const { confirm } = useContext(ModalContext); + + const [isAnonEnabled, setIsAnonEnabled] = useState() + const [daikokuId, setDaikokuId] = useState() + const [lastResponseDate, setLastResponseDate] = useState() + const currentDate = new Date(); + const sixMonthsAgo = new Date(new Date().setMonth(currentDate.getMonth() - 6)); + + + + useEffect(() => { + Services.getAnonymousState().then(res =>{ + setIsAnonEnabled(res.activated) + setDaikokuId(res.id) + setLastResponseDate(res.date) + }) + }, []); + + useEffect(() => { + if(isAnonEnabled === false && connectedUser.isDaikokuAdmin && daikokuId && (!lastResponseDate || new Date(lastResponseDate) < sixMonthsAgo)) { + confirm({title: translate('Enable Anonymous reporting'), message: translate('Enable Anonymous reporting for helping us ? This will help us a lot. More info at (url Doc)'), okLabel: translate('Yes') }) //TODO lien vers la doc + .then((ok) => { + if (ok) { + Services.updateAnonymousState(daikokuId, true, currentDate.getTime()).then(() => { + toastr.success(translate('Success'), translate("anonymous.reporting.success.enabled")) + }) + } else { + Services.updateAnonymousState(daikokuId, false, currentDate.getTime()).then(() => { + toastr.info(translate('Info'), translate("anonymous.reporting.popup.no")) + }) + } + }); + } + }, [isAnonEnabled]); const redirectToApiPage = (apiWithAutho: IApiWithAuthorization) => { @@ -58,6 +98,8 @@ export const MyHome = () => { } }; + + if (myTeamsRequest.isLoading) { return ( diff --git a/daikoku/javascript/src/locales/en/translation.json b/daikoku/javascript/src/locales/en/translation.json index f0dbd02d..6cd49c17 100644 --- a/daikoku/javascript/src/locales/en/translation.json +++ b/daikoku/javascript/src/locales/en/translation.json @@ -1372,5 +1372,10 @@ "aggregated.apikey.badge.title": "Aggregated API key", "aggregated.apikey.badge.apikey.name": "API key custom name", "apisubscription.lastUsage.label": "Last usage", - "N/A": "N/A" + "N/A": "N/A", + "anonymous.reporting.success.enabled" : "Anonymous reporting enabled.", + "anonymous.reporting.success.disabled" : "Anonymous reporting disabled.", + "anonymous.reporting.error": "Error while changing value.", + "anonymous.reporting.popup.info": "Enable Anonymous reporting for helping us ? This will help us a lot. More info at (url Doc).", + "anonymous.reporting.popup.no": "Anonymous Reporting not accepted." } \ No newline at end of file diff --git a/daikoku/javascript/src/locales/fr/translation.json b/daikoku/javascript/src/locales/fr/translation.json index 812b4671..6a2eb8a8 100644 --- a/daikoku/javascript/src/locales/fr/translation.json +++ b/daikoku/javascript/src/locales/fr/translation.json @@ -1376,5 +1376,10 @@ "aggregated.apikey.badge.title": "Clé d'API aggrégée", "aggregated.apikey.badge.apikey.name": "Nom personnalisée de la clé", "apisubscription.lastUsage.label": "Last usage", - "N/A": "N/A" + "N/A": "N/A", + "anonymous.reporting.success.enabled" : "Rapports anonymes activés.", + "anonymous.reporting.success.disabled" : "Rapports anonymes désactivés.", + "anonymous.reporting.error": "Erreur lors du changement de valeur.", + "anonymous.reporting.popup.info": "Activer les rapports anonymes pour nous aider ? Cela nous aidera beaucoup. Plus d'info ici : (url Doc).", + "anonymous.reporting.popup.no": "Rapports anonymes non acceptés." } \ No newline at end of file diff --git a/daikoku/javascript/src/services/index.ts b/daikoku/javascript/src/services/index.ts index 8dd2c9bf..b2855570 100644 --- a/daikoku/javascript/src/services/index.ts +++ b/daikoku/javascript/src/services/index.ts @@ -19,6 +19,7 @@ import { IUser, IUserSimple, ISimpleOtoroshiSettings, + IAnonymousState, } from '../types'; import { ResponseError, @@ -570,6 +571,12 @@ export const deleteSessions = () => method: 'DELETE', }); +export const getAnonymousState = (): Promise => customFetch('/api/state/anonymous'); +export const updateAnonymousState = (id: string, value: boolean, currentDate?: number) => customFetch('/api/state/anonymous', { + method: 'POST', + body: JSON.stringify({ id, value, currentDate}), +}); + export const search = (search: any) => customFetch('/api/_search', { method: 'POST', diff --git a/daikoku/javascript/src/types/context.ts b/daikoku/javascript/src/types/context.ts index cd8ce408..e97d1ac4 100644 --- a/daikoku/javascript/src/types/context.ts +++ b/daikoku/javascript/src/types/context.ts @@ -27,3 +27,9 @@ export interface IStateContext { export interface IStateError { status: number; } + +export interface IAnonymousState { + activated: boolean + id: string + date?: number +} From a1edb8c442955964bbc89189d02d721a139ed90e Mon Sep 17 00:00:00 2001 From: btxgear Date: Tue, 23 Apr 2024 15:12:43 +0200 Subject: [PATCH 3/3] Added documentation --- daikoku/app/env/env.scala | 1 + daikoku/app/jobs/AnonymousReportingJob.scala | 6 +- daikoku/build.sbt | 1 + daikoku/conf/base.conf | 2 + .../anonymousreporting/AnonymousReporting.tsx | 5 +- .../src/components/frontend/team/MyHome.tsx | 2 +- .../src/locales/en/translation.json | 5 +- .../src/locales/fr/translation.json | 5 +- .../01-getstarted/05-firstrun/configfile.md | 49 ++++++++----- manual/docs/01-getstarted/05-firstrun/env.md | 3 + .../docs/01-getstarted/06-setup/reporting.md | 69 +++++++++++++++++++ package-lock.json | 6 ++ 12 files changed, 130 insertions(+), 24 deletions(-) create mode 100644 manual/docs/01-getstarted/06-setup/reporting.md create mode 100644 package-lock.json diff --git a/daikoku/app/env/env.scala b/daikoku/app/env/env.scala index 2dbe5f08..303025a4 100644 --- a/daikoku/app/env/env.scala +++ b/daikoku/app/env/env.scala @@ -246,6 +246,7 @@ class Config(val underlying: Configuration) { lazy val anonymousReportingUrl: String = underlying.get[String]("daikoku.anonymous-reporting.url") lazy val anonymousReportingTimeout: Int = underlying.get[Int]("daikoku.anonymous-reporting.timeout") lazy val anonymousReportingEnabled: Boolean = underlying.get[Boolean]("daikoku.anonymous-reporting.enabled") + lazy val containerized: Boolean = underlying.get[Boolean]("daikoku.containerized") } sealed trait Env { diff --git a/daikoku/app/jobs/AnonymousReportingJob.scala b/daikoku/app/jobs/AnonymousReportingJob.scala index d662e7c2..1aaad9df 100644 --- a/daikoku/app/jobs/AnonymousReportingJob.scala +++ b/daikoku/app/jobs/AnonymousReportingJob.scala @@ -59,6 +59,7 @@ class AnonymousReportingJob(env: Env) { private val wSRequest: WSRequest = env.wsClient.url(ev.config.anonymousReportingUrl) private val enabled: Boolean = ev.config.anonymousReportingEnabled + private val containerized: Boolean = ev.config.containerized private val dataStore = ev.dataStore def start(): Unit = { @@ -197,12 +198,13 @@ class AnonymousReportingJob(env: Env) { "daikoku_version" -> data.daikokuVersion, "java_version" -> data.javaVersion, "os" -> data.os, + "containerized" -> containerized, ) _ <- wSRequest.withRequestTimeout(ev.config.anonymousReportingTimeout.millis).post(post).map { resp => if (resp.status != 200 && resp.status != 201 && resp.status != 204) { logger.error(s"error while sending anonymous reports: ${resp.status} - ${resp.body}") } else { - logger.info("Thank you for having anonymous reporting enabled, Data sent ! For more info see (url vers la doc)") //TODO Faire la doc et rjaouter l'URL + logger.info("Thank you for having anonymous reporting enabled, Data sent ! For more info see (https://maif.github.io/daikoku/docs/getstarted/setup/reporting)") } } .recover { case e: Throwable => @@ -212,7 +214,7 @@ class AnonymousReportingJob(env: Env) { } yield Done } else { - logger.info("Anonymous reporting is disabled if you want to activate it for helping us, see (url vers la doc)") //TODO tout pareil + logger.info("Anonymous reporting is disabled if you want to activate it for helping us, see (https://maif.github.io/daikoku/docs/getstarted/setup/reporting)") FastFuture.successful(Done) } }).flatten diff --git a/daikoku/build.sbt b/daikoku/build.sbt index 3858babc..9231720f 100644 --- a/daikoku/build.sbt +++ b/daikoku/build.sbt @@ -172,6 +172,7 @@ dockerCommands := dockerCommands.value.filterNot { Docker / dockerPackageMappings += (baseDirectory.value / "docker" / "start.sh") -> "/opt/docker/bin/start.sh" dockerEntrypoint := Seq("/opt/docker/bin/start.sh") dockerUpdateLatest := true +dockerEnvVars := Map("daikoku.containerized"-> "true") import sbtrelease.ReleasePlugin.autoImport.ReleaseTransformations._ diff --git a/daikoku/conf/base.conf b/daikoku/conf/base.conf index ef098ca3..485e6da1 100644 --- a/daikoku/conf/base.conf +++ b/daikoku/conf/base.conf @@ -13,6 +13,8 @@ daikoku { storage = "postgres" storage = ${?DAIKOKU_STORAGE} + containerized = false + containerized = ${?IS_CONTAINERIZED} anonymous-reporting { enabled = true diff --git a/daikoku/javascript/src/components/adminbackoffice/anonymousreporting/AnonymousReporting.tsx b/daikoku/javascript/src/components/adminbackoffice/anonymousreporting/AnonymousReporting.tsx index dce0488c..9d4b737d 100644 --- a/daikoku/javascript/src/components/adminbackoffice/anonymousreporting/AnonymousReporting.tsx +++ b/daikoku/javascript/src/components/adminbackoffice/anonymousreporting/AnonymousReporting.tsx @@ -40,11 +40,14 @@ export const AnonymousReporting = () => {
- +
+
+ More information at Daikoku documentation
+
) diff --git a/daikoku/javascript/src/components/frontend/team/MyHome.tsx b/daikoku/javascript/src/components/frontend/team/MyHome.tsx index 7982e93b..e335c305 100644 --- a/daikoku/javascript/src/components/frontend/team/MyHome.tsx +++ b/daikoku/javascript/src/components/frontend/team/MyHome.tsx @@ -52,7 +52,7 @@ export const MyHome = () => { useEffect(() => { if(isAnonEnabled === false && connectedUser.isDaikokuAdmin && daikokuId && (!lastResponseDate || new Date(lastResponseDate) < sixMonthsAgo)) { - confirm({title: translate('Enable Anonymous reporting'), message: translate('Enable Anonymous reporting for helping us ? This will help us a lot. More info at (url Doc)'), okLabel: translate('Yes') }) //TODO lien vers la doc + confirm({title: translate('anonymous.reporting.enable'), message:
{translate('anonymous.reporting.popup.info')} Daikoku documentation
, okLabel: translate('Yes') }) .then((ok) => { if (ok) { Services.updateAnonymousState(daikokuId, true, currentDate.getTime()).then(() => { diff --git a/daikoku/javascript/src/locales/en/translation.json b/daikoku/javascript/src/locales/en/translation.json index 6cd49c17..32ff8fce 100644 --- a/daikoku/javascript/src/locales/en/translation.json +++ b/daikoku/javascript/src/locales/en/translation.json @@ -1376,6 +1376,7 @@ "anonymous.reporting.success.enabled" : "Anonymous reporting enabled.", "anonymous.reporting.success.disabled" : "Anonymous reporting disabled.", "anonymous.reporting.error": "Error while changing value.", - "anonymous.reporting.popup.info": "Enable Anonymous reporting for helping us ? This will help us a lot. More info at (url Doc).", - "anonymous.reporting.popup.no": "Anonymous Reporting not accepted." + "anonymous.reporting.popup.info": "Enable Anonymous reporting for helping us ? This will help us a lot. More info in ", + "anonymous.reporting.popup.no": "Anonymous Reporting not accepted.", + "anonymous.reporting.enable": "Enable anonymous reporting" } \ No newline at end of file diff --git a/daikoku/javascript/src/locales/fr/translation.json b/daikoku/javascript/src/locales/fr/translation.json index 6a2eb8a8..2d365f0e 100644 --- a/daikoku/javascript/src/locales/fr/translation.json +++ b/daikoku/javascript/src/locales/fr/translation.json @@ -1380,6 +1380,7 @@ "anonymous.reporting.success.enabled" : "Rapports anonymes activés.", "anonymous.reporting.success.disabled" : "Rapports anonymes désactivés.", "anonymous.reporting.error": "Erreur lors du changement de valeur.", - "anonymous.reporting.popup.info": "Activer les rapports anonymes pour nous aider ? Cela nous aidera beaucoup. Plus d'info ici : (url Doc).", - "anonymous.reporting.popup.no": "Rapports anonymes non acceptés." + "anonymous.reporting.popup.info": "Activer les rapports anonymes pour nous aider ? Cela nous aidera beaucoup. Pour plus d'informations, cliquez sur : ", + "anonymous.reporting.popup.no": "Rapports anonymes non acceptés.", + "anonymous.reporting.enable": "Activer les rapports anonymes" } \ No newline at end of file diff --git a/manual/docs/01-getstarted/05-firstrun/configfile.md b/manual/docs/01-getstarted/05-firstrun/configfile.md index 4983a596..1818c887 100644 --- a/manual/docs/01-getstarted/05-firstrun/configfile.md +++ b/manual/docs/01-getstarted/05-firstrun/configfile.md @@ -84,22 +84,28 @@ or As Daikoku is a [Play app](https://www.playframework.com/), you should take a look at [Play configuration documentation](https://www.playframework.com/documentation/2.6.x/Configuration) to tune its internal configuration -| name | type | default value | description | -| ---- |:----:| -------------- | ----- | -| `http.port` | number | 8080 | the http port used by Daikoku. You can use 'disabled' as value if you don't want to use http | -| `https.port` | number | disabled | the https port used by Daikoku. You can use 'disabled' as value if you don't want to use https | -| `play.http.secret.key` | string | "secret" | the secret used to sign Daikoku session cookie | -| `play.http.session.secure` | boolean | false | whether or not the Daikoku backoffice session will be served over https only | -| `play.http.session.httpOnly` | boolean | true | whether or not the Daikoku backoffice session will be accessible from Javascript | -| `play.http.session.maxAge` | number | 259200000 | the number of seconds before Daikoku backoffice session expired | -| `play.http.session.domain` | string | null | the domain on which the Daikoku backoffice session is authorized | -| `play.http.session.cookieName` | string | "daikoku-session" | the name of the Daikoku backoffice session | -| `server.https.keyStore.path` | string | | the path to the keyStore containing the private key and certificate, if not provided generates a keystore for you | -| `server.https.keyStore.type` | string | "JKS" | the keyStore type | -| `server.https.keyStore.password` | string | "" | the password | -| `server.https.keyStore.algorithm` | string | | The keyStore algorithm, defaults to the platforms default algorithm | - - +| name | type | default value | description | +|-----------------------------------|:-------:|-------------------|-------------------------------------------------------------------------------------------------------------------| +| `http.port` | number | 8080 | the http port used by Daikoku. You can use 'disabled' as value if you don't want to use http | +| `https.port` | number | disabled | the https port used by Daikoku. You can use 'disabled' as value if you don't want to use https | +| `play.http.secret.key` | string | "secret" | the secret used to sign Daikoku session cookie | +| `play.http.session.secure` | boolean | false | whether or not the Daikoku backoffice session will be served over https only | +| `play.http.session.httpOnly` | boolean | true | whether or not the Daikoku backoffice session will be accessible from Javascript | +| `play.http.session.maxAge` | number | 259200000 | the number of seconds before Daikoku backoffice session expired | +| `play.http.session.domain` | string | null | the domain on which the Daikoku backoffice session is authorized | +| `play.http.session.cookieName` | string | "daikoku-session" | the name of the Daikoku backoffice session | +| `server.https.keyStore.path` | string | | the path to the keyStore containing the private key and certificate, if not provided generates a keystore for you | +| `server.https.keyStore.type` | string | "JKS" | the keyStore type | +| `server.https.keyStore.password` | string | "" | the password | +| `server.https.keyStore.algorithm` | string | | The keyStore algorithm, defaults to the platforms default algorithm | + +## Anonymous reporting + +| name | type | default value | description | +|---------------------------------------|:-------:|---------------|-----------------------------------------------------------------------------------------------------------------------| +| `daikoku.anonymous-reporting.enabled` | boolean | true | Enabling or not the anonymous reporting. If it's set to true and in the frontend to false, no reporting will be sent. | +| `daikoku.anonymous-reporting.timeout` | number | 60000 | The request timeout for sending data to our anonymous reporting database. | +| `daikoku.containerized` | boolean | false | This is to know if you are running daikoku with docker or not (only used for the anonymous reporting) | ## More config. options @@ -183,6 +189,17 @@ daikoku { max.date = 60days } } + + containerized = false + containerized = ${?IS_CONTAINERIZED} + + anonymous-reporting { + enabled = true + enabled = ${?DAIKOKU_ANONYMOUS_REPORTING_ENABLED} + url = "https://reporting.otoroshi.io/daikoku/ingest" + timeout = 60000 + timeout = ${?DAIKOKU_ANONYMOUS_REPORTING_TIMEOUT} + } } postgres { diff --git a/manual/docs/01-getstarted/05-firstrun/env.md b/manual/docs/01-getstarted/05-firstrun/env.md index 49154b0f..9cf147df 100644 --- a/manual/docs/01-getstarted/05-firstrun/env.md +++ b/manual/docs/01-getstarted/05-firstrun/env.md @@ -56,4 +56,7 @@ trusted-cert = ${?DAIKOKU_POSTGRES_SSL_TRUSTED_CERT} client-cert-path = ${?DAIKOKU_POSTGRES_SSL_CLIENT_CERT_PATH} client-cert = ${?DAIKOKU_POSTGRES_SSL_CLIENT_CERT} trust-all = ${?DAIKOKU_POSTGRES_SSL_TRUST_ALL} +containerized = ${?IS_CONTAINERIZED} +anonymous-report-enabled = ${?DAIKOKU_ANONYMOUS_REPORTING_ENABLED} +timeout = ${?DAIKOKU_ANONYMOUS_REPORTING_TIMEOUT} ``` diff --git a/manual/docs/01-getstarted/06-setup/reporting.md b/manual/docs/01-getstarted/06-setup/reporting.md new file mode 100644 index 00000000..bca4c167 --- /dev/null +++ b/manual/docs/01-getstarted/06-setup/reporting.md @@ -0,0 +1,69 @@ +# Anonymous reporting +The best way of supporting us in Daikoku development is to enable Anonymous reporting. + +## Details +When this feature is active, Otoroshi periodically send anonymous information about its configuration. + +This information helps us to know how Daikoku is used, it’s a precious hint to prioritise our roadmap. + +Below is an example of what is sent by Daikoku. You can also [read the source code](https://github.com/MAIF/daikoku/blob/master/daikoku/app/jobs/AnonymousReportingJob.scala#L80-L147). + +```json +{ + "os": { + "arch": "aarch64", + "name": "Mac OS X", + "version": "14.4.1" + }, + "@id": "d25c3bc2c-647a-47d6-8e8a-ee5b32cc0822", + "tenants": [ + { + "AuthRole": "Local", + "payements": 0, + "tenantMode": "Default", + "visibility": "public", + "displayMode": "default" + } + ], + "entities": { + "apis": 3, + "teams": 6, + "users": 3, + "cmspages": 0, + "messages": 0, + "api_posts": 0, + "api_issues": 0, + "evolutions": 0, + "operations": 0, + "usage_plans": 0, + "audit_events": 0, + "consumptions": 1908, + "translations": 0, + "notifications": 2, + "user_sessions": 0, + "password_reset": 0, + "step_validator": 0, + "account_creation": 0, + "api_subscription": 2, + "email_verifications": 0, + "subscription_demands": 44, + "api_documentation_pages": 1 + }, + "features": {}, + "timestamp": 1712048893066, + "java_version": { + "vendor": "Eclipse Adoptium", + "version": "11.0.16.1" + }, + "timestamp_str": "2024-04-02T11:08:13.066+02:00", + "daikoku_version": "17.2.0-dev", + "daikoku_cluster_id": "dade842c5-0c2c-4a2d-acec-05db7c1ce36d" +} +``` + +## Toggling +Anonymous reporting can be toggled any time using : + +- the UI (Daikoku settings > Anonymous reporting) +- daikoku.anonymous-reporting.enabled configuration +- DAIKOKU_ANONYMOUS_REPORTING_ENABLED env variable \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..76382322 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "daikoku", + "lockfileVersion": 3, + "requires": true, + "packages": {} +}