diff --git a/server/AyaNova/Controllers/BackupController.cs b/server/AyaNova/Controllers/BackupController.cs index bf352862..00601061 100644 --- a/server/AyaNova/Controllers/BackupController.cs +++ b/server/AyaNova/Controllers/BackupController.cs @@ -61,7 +61,7 @@ namespace AyaNova.Api.Controllers { if (serverState.IsClosed) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); - if (!Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull)) + if (!Authorized.HasModifyRole(HttpContext.Items, AyaType.Backup))//technically maybe this could be wider open, but for now keeping as locked down return StatusCode(403, new ApiNotAuthorizedResponse()); var JobName = $"Backup (on demand)"; OpsJob j = new OpsJob(); @@ -87,7 +87,7 @@ namespace AyaNova.Api.Controllers //Need size and more info if (serverState.IsClosed) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); - if (!Authorized.HasAnyRole(HttpContext.Items, AuthorizationRoles.OpsAdminFull | AuthorizationRoles.OpsAdminLimited)) + if (!Authorized.HasReadFullRole(HttpContext.Items, AyaType.Backup)) return StatusCode(403, new ApiNotAuthorizedResponse()); return Ok(ApiOkResponse.Response(FileUtil.BackupStatusReport())); } @@ -122,7 +122,7 @@ namespace AyaNova.Api.Controllers return StatusCode(401, new ApiErrorResponse(ApiErrorCode.AUTHENTICATION_FAILED)); } - if (!Authorized.HasAnyRole(DownloadUser.Roles, AuthorizationRoles.OpsAdminFull)) + if (!Authorized.HasModifyRole(HttpContext.Items, AyaType.Backup))//not technically modify but treating as such as a backup is very sensitive data { await Task.Delay(nFailedAuthDelay);//DOS protection return StatusCode(403, new ApiNotAuthorizedResponse()); diff --git a/server/AyaNova/Controllers/ServerMetricsController.cs b/server/AyaNova/Controllers/ServerMetricsController.cs index c23c2dcf..bb4713e5 100644 --- a/server/AyaNova/Controllers/ServerMetricsController.cs +++ b/server/AyaNova/Controllers/ServerMetricsController.cs @@ -94,7 +94,7 @@ namespace AyaNova.Api.Controllers //Note: the date and times are nullable and required so that the regular modelstate code kicks in to ensure they are present if (serverState.IsClosed) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); - if (!Authorized.HasReadFullRole(HttpContext.Items, AyaType.Metrics)) + if (!Authorized.HasReadFullRole(HttpContext.Items, AyaType.ServerMetrics)) return StatusCode(403, new ApiNotAuthorizedResponse()); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); @@ -127,7 +127,7 @@ namespace AyaNova.Api.Controllers privateBytes = dsPrivateBytes.Select(z => new MetricLong(DateTime.FromOADate(z.Item1), z.Item2 / MB)).ToArray() }; - await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, AyaType.Metrics, AyaEvent.Retrieved), ct); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, AyaType.ServerMetrics, AyaEvent.Retrieved), ct); return Ok(ApiOkResponse.Response(ret)); } @@ -145,7 +145,7 @@ namespace AyaNova.Api.Controllers //Note: the date and times are nullable and required so that the regular modelstate code kicks in to ensure they are present if (serverState.IsClosed) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); - if (!Authorized.HasReadFullRole(HttpContext.Items, AyaType.Metrics)) + if (!Authorized.HasReadFullRole(HttpContext.Items, AyaType.ServerMetrics)) return StatusCode(403, new ApiNotAuthorizedResponse()); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); @@ -189,7 +189,7 @@ namespace AyaNova.Api.Controllers - await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, AyaType.Metrics, AyaEvent.Retrieved), ct); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, AyaType.ServerMetrics, AyaEvent.Retrieved), ct); return Ok(ApiOkResponse.Response(ret)); } @@ -208,7 +208,7 @@ namespace AyaNova.Api.Controllers //Note: the date and times are nullable and required so that the regular modelstate code kicks in to ensure they are present if (serverState.IsClosed) return StatusCode(503, new ApiErrorResponse(serverState.ApiErrorCode, null, serverState.Reason)); - if (!Authorized.HasReadFullRole(HttpContext.Items, AyaType.Metrics)) + if (!Authorized.HasReadFullRole(HttpContext.Items, AyaType.ServerMetrics)) return StatusCode(403, new ApiNotAuthorizedResponse()); if (!ModelState.IsValid) return BadRequest(new ApiErrorResponse(ModelState)); @@ -296,7 +296,7 @@ namespace AyaNova.Api.Controllers - await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, AyaType.Metrics, AyaEvent.Retrieved), ct); + await EventLogProcessor.LogEventToDatabaseAsync(new Event(UserIdFromContext.Id(HttpContext.Items), 0, AyaType.ServerMetrics, AyaEvent.Retrieved), ct); return Ok(ApiOkResponse.Response(ret)); } diff --git a/server/AyaNova/Startup.cs b/server/AyaNova/Startup.cs index 5f84ff9e..da1488a4 100644 --- a/server/AyaNova/Startup.cs +++ b/server/AyaNova/Startup.cs @@ -416,7 +416,7 @@ namespace AyaNova var utcNow = new DateTimeOffset(DateTime.Now.ToUniversalTime(), TimeSpan.Zero); if (u.DlKeyExpire > utcNow.DateTime) { - if (AyaNova.Api.ControllerHelpers.Authorized.HasReadFullRole(u.Roles, AyaType.Metrics)) + if (AyaNova.Api.ControllerHelpers.Authorized.HasReadFullRole(u.Roles, AyaType.ServerMetrics)) context.Request.HttpContext.Items["AY_PROFILER_ALLOWED"] = true; } } diff --git a/server/AyaNova/biz/AuthorizationRoles.cs b/server/AyaNova/biz/AuthorizationRoles.cs index 890ad1c3..adc2b4b5 100644 --- a/server/AyaNova/biz/AuthorizationRoles.cs +++ b/server/AyaNova/biz/AuthorizationRoles.cs @@ -64,6 +64,8 @@ namespace AyaNova.Biz InventoryFull | AccountingFull | TechLimited | TechFull | SubContractorLimited | SubContractorFull | OpsAdminLimited | OpsAdminFull | SalesFull | SalesLimited + + }//end AuthorizationRoles //, 65536, 131072, 262144, 524288, 1,048,576 }//end namespace GZTW.AyaNova.BLL diff --git a/server/AyaNova/biz/AyaType.cs b/server/AyaNova/biz/AyaType.cs index 83c0586b..9e44c971 100644 --- a/server/AyaNova/biz/AyaType.cs +++ b/server/AyaNova/biz/AyaType.cs @@ -37,7 +37,7 @@ namespace AyaNova.Biz [CoreBizObject] Contract = 10, TrialSeeder = 11, - Metrics = 12, + ServerMetrics = 12, Translation = 13, UserOptions = 14, [CoreBizObject] @@ -103,7 +103,11 @@ namespace AyaNova.Biz WorkOrderTemplate = 45, [CoreBizObject] WorkOrderTemplateItem = 46, - GlobalOps=47 + GlobalOps=47, + BizMetrics=48, + Backup=49, + Notification=50, + NotificationSubscription=51 //NOTE: New objects added here need to also be added to the following classes: diff --git a/server/AyaNova/biz/BizRoles.cs b/server/AyaNova/biz/BizRoles.cs index 6e7392ab..4f840cf7 100644 --- a/server/AyaNova/biz/BizRoles.cs +++ b/server/AyaNova/biz/BizRoles.cs @@ -410,6 +410,15 @@ namespace AyaNova.Biz }); + //////////////////////////////////////////////////////////// + //BACKUP + //Only opsfull can change Backup + //ops and biz admin can view Backup + roles.Add(AyaType.Backup, new BizRoleSet() + { + Change = AuthorizationRoles.OpsAdminFull, + ReadFullRecord = AuthorizationRoles.OpsAdminLimited | AuthorizationRoles.BizAdminFull | AuthorizationRoles.BizAdminLimited + }); //////////////////////////////////////////////////////////// @@ -424,9 +433,9 @@ namespace AyaNova.Biz //////////////////////////////////////////////////////////// - //METRICS + //SERVERMETRICS // - roles.Add(AyaType.Metrics, new BizRoleSet() + roles.Add(AyaType.ServerMetrics, new BizRoleSet() { Change = AuthorizationRoles.OpsAdminFull,//this is to turn on extra metrics (profiler) ReadFullRecord = AuthorizationRoles.OpsAdminFull | AuthorizationRoles.OpsAdminLimited @@ -477,6 +486,38 @@ namespace AyaNova.Biz ReadFullRecord = AuthorizationRoles.All }); + //////////////////////////////////////////////////////////// + //BIZMETRICS + // + roles.Add(AyaType.BizMetrics, new BizRoleSet() + { + Change = AuthorizationRoles.BizAdminFull, + ReadFullRecord = AuthorizationRoles.BizAdminLimited | + AuthorizationRoles.SalesFull | + AuthorizationRoles.SalesLimited | + AuthorizationRoles.AccountingFull + }); + + //////////////////////////////////////////////////////////// + //NOTIFICATION + // + roles.Add(AyaType.Notification, new BizRoleSet() + { + Change = AuthorizationRoles.All, + ReadFullRecord = AuthorizationRoles.All + }); + + //////////////////////////////////////////////////////////// + //NOTIFICATION_SUBSCRIPTION + // + roles.Add(AyaType.NotificationSubscription, new BizRoleSet() + { + Change = AuthorizationRoles.All, + ReadFullRecord = AuthorizationRoles.All + }); + + + //////////////////////////////////////////////////////////////////// #endregion all roles init @@ -497,7 +538,7 @@ namespace AyaNova.Biz //ONGOING VALIDATION TO CATCH MISMATCH WHEN NEW ROLES ADDED (wont' catch changes to existing unfortunately) //var lastRoles = "{\"User\":{\"Change\":2,\"ReadFullRecord\":1,\"Select\":131071},\"UserOptions\":{\"Change\":2,\"ReadFullRecord\":1,\"Select\":0},\"Widget\":{\"Change\":34,\"ReadFullRecord\":17,\"Select\":131071},\"ServerState\":{\"Change\":16384,\"ReadFullRecord\":131071,\"Select\":0},\"License\":{\"Change\":16386,\"ReadFullRecord\":8193,\"Select\":0},\"LogFile\":{\"Change\":0,\"ReadFullRecord\":24576,\"Select\":0},\"ServerJob\":{\"Change\":16384,\"ReadFullRecord\":8195,\"Select\":0},\"AyaNova7Import\":{\"Change\":16384,\"ReadFullRecord\":0,\"Select\":0},\"Metrics\":{\"Change\":0,\"ReadFullRecord\":24576,\"Select\":0},\"Translation\":{\"Change\":16386,\"ReadFullRecord\":131071,\"Select\":0},\"DataListView\":{\"Change\":2,\"ReadFullRecord\":131071,\"Select\":0},\"FormCustom\":{\"Change\":2,\"ReadFullRecord\":131071,\"Select\":0},\"PickListTemplate\":{\"Change\":2,\"ReadFullRecord\":131071,\"Select\":131071}}"; - var lastRoles = "{\"Customer\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Contract\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"HeadOffice\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"LoanUnit\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Part\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"PM\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"PMItem\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"PMTemplate\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"PMTemplateItem\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Project\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"PurchaseOrder\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Quote\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"QuoteItem\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"QuoteTemplate\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"QuoteTemplateItem\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Unit\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"UnitModel\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Vendor\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrder\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItem\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemExpense\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemLabor\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemLoan\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemPart\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemPartRequest\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemScheduledUser\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemTask\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemTravel\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemUnit\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderTemplate\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderTemplateItem\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Global\":{\"Change\":2,\"ReadFullRecord\":1,\"Select\":0},\"GlobalOps\":{\"Change\":16384,\"ReadFullRecord\":8192,\"Select\":0},\"User\":{\"Change\":2,\"ReadFullRecord\":1,\"Select\":131071},\"UserOptions\":{\"Change\":2,\"ReadFullRecord\":1,\"Select\":0},\"Widget\":{\"Change\":34,\"ReadFullRecord\":17,\"Select\":131071},\"ServerState\":{\"Change\":16384,\"ReadFullRecord\":131071,\"Select\":0},\"License\":{\"Change\":16386,\"ReadFullRecord\":8193,\"Select\":0},\"LogFile\":{\"Change\":0,\"ReadFullRecord\":24576,\"Select\":0},\"ServerJob\":{\"Change\":16384,\"ReadFullRecord\":8195,\"Select\":0},\"Metrics\":{\"Change\":0,\"ReadFullRecord\":24576,\"Select\":0},\"Translation\":{\"Change\":16386,\"ReadFullRecord\":131071,\"Select\":0},\"DataListView\":{\"Change\":2,\"ReadFullRecord\":131071,\"Select\":0},\"FormCustom\":{\"Change\":2,\"ReadFullRecord\":131071,\"Select\":0},\"PickListTemplate\":{\"Change\":2,\"ReadFullRecord\":131071,\"Select\":0}}"; + var lastRoles = "{\"Customer\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Contract\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"HeadOffice\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"LoanUnit\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Part\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"PM\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"PMItem\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"PMTemplate\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"PMTemplateItem\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Project\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"PurchaseOrder\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Quote\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"QuoteItem\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"QuoteTemplate\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"QuoteTemplateItem\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Unit\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"UnitModel\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Vendor\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrder\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItem\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemExpense\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemLabor\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemLoan\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemPart\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemPartRequest\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemScheduledUser\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemTask\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemTravel\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderItemUnit\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderTemplate\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"WorkOrderTemplateItem\":{\"Change\":33098,\"ReadFullRecord\":65669,\"Select\":131071},\"Global\":{\"Change\":2,\"ReadFullRecord\":1,\"Select\":0},\"GlobalOps\":{\"Change\":16384,\"ReadFullRecord\":8192,\"Select\":0},\"User\":{\"Change\":2,\"ReadFullRecord\":1,\"Select\":131071},\"UserOptions\":{\"Change\":2,\"ReadFullRecord\":1,\"Select\":0},\"Widget\":{\"Change\":34,\"ReadFullRecord\":17,\"Select\":131071},\"ServerState\":{\"Change\":16384,\"ReadFullRecord\":131071,\"Select\":0},\"License\":{\"Change\":16386,\"ReadFullRecord\":8193,\"Select\":0},\"LogFile\":{\"Change\":0,\"ReadFullRecord\":24576,\"Select\":0},\"ServerJob\":{\"Change\":16384,\"ReadFullRecord\":8195,\"Select\":0},\"ServerMetrics\":{\"Change\":0,\"ReadFullRecord\":24576,\"Select\":0},\"Translation\":{\"Change\":16386,\"ReadFullRecord\":131071,\"Select\":0},\"DataListView\":{\"Change\":2,\"ReadFullRecord\":131071,\"Select\":0},\"FormCustom\":{\"Change\":2,\"ReadFullRecord\":131071,\"Select\":0},\"PickListTemplate\":{\"Change\":2,\"ReadFullRecord\":131071,\"Select\":0}}"; Dictionary lastRolesDeserialized = Newtonsoft.Json.JsonConvert.DeserializeObject>(lastRoles); diff --git a/server/AyaNova/biz/GlobalOpsBackupSettingsBiz.cs b/server/AyaNova/biz/GlobalOpsBackupSettingsBiz.cs index 5e02f9e8..ccb4c844 100644 --- a/server/AyaNova/biz/GlobalOpsBackupSettingsBiz.cs +++ b/server/AyaNova/biz/GlobalOpsBackupSettingsBiz.cs @@ -17,7 +17,7 @@ namespace AyaNova.Biz UserId = currentUserId; UserTranslationId = userTranslationId; CurrentUserRoles = UserRoles; - BizType = AyaType.GlobalOps; + BizType = AyaType.Backup; } internal static GlobalOpsBackupSettingsBiz GetBiz(AyContext ct, Microsoft.AspNetCore.Http.HttpContext httpContext = null) diff --git a/server/AyaNova/resource/de.json b/server/AyaNova/resource/de.json index b0dbdb14..8ddd06e2 100644 --- a/server/AyaNova/resource/de.json +++ b/server/AyaNova/resource/de.json @@ -1658,7 +1658,7 @@ "ServerJob": "Serverjob", "AyaNova7Import": "AyaNova 7 importieren", "TrialSeeder": "Probesaatgutdaten", - "Metrics": "Metrik", + "BizMetrics": "Metrik", "UserOptions": "Benutzeroptionen", "FileAttachment": "Dateianhang", "FormCustom": "Formularanpassung", diff --git a/server/AyaNova/resource/en.json b/server/AyaNova/resource/en.json index a2a4165b..354bf10d 100644 --- a/server/AyaNova/resource/en.json +++ b/server/AyaNova/resource/en.json @@ -1658,7 +1658,7 @@ "ServerJob": "Server job", "AyaNova7Import": "AyaNova 7 import", "TrialSeeder": "Trial seed data", - "Metrics": "Metrics", + "BizMetrics": "Metrics", "UserOptions": "User options", "FileAttachment": "Attachment", "FormCustom": "Form customization", diff --git a/server/AyaNova/resource/es.json b/server/AyaNova/resource/es.json index 0aa443e5..378f0ea6 100644 --- a/server/AyaNova/resource/es.json +++ b/server/AyaNova/resource/es.json @@ -1658,7 +1658,7 @@ "ServerJob": "Trabajo del servidor", "AyaNova7Import": "Importación de AyaNova 7", "TrialSeeder": "Datos iniciales de prueba", - "Metrics": "Métricas", + "BizMetrics": "Métricas", "UserOptions": "Opciones de usuario", "FileAttachment": "Archivo adjunto", "FormCustom": "Personalización de formularios", diff --git a/server/AyaNova/resource/fr.json b/server/AyaNova/resource/fr.json index cd1589a7..826a475f 100644 --- a/server/AyaNova/resource/fr.json +++ b/server/AyaNova/resource/fr.json @@ -1658,7 +1658,7 @@ "ServerJob": "Travail serveur", "AyaNova7Import": "Fichier d'importation AyaNova 7", "TrialSeeder": "Données sur les semences d'essai", - "Metrics": "Indicateurs de performance", + "BizMetrics": "Indicateurs de performance", "UserOptions": "Options utilisateur", "FileAttachment": "Pièce jointe", "FormCustom": "Personnalisation des formulaires",