diff options
86 files changed, 2077 insertions, 1077 deletions
@@ -9,3 +9,7 @@ backend/microservice/temp/ backend/microservice/api/__pycache__/ production/app/node_modules/ production/app/dist/ +backend/microservice/api/temp/ +backend/microservice/Boston.csv +backend/microservice/diamonds.csv +backend/microservice/IMDB-Movie-Data.csv diff --git a/backend/api/api/.gitignore b/backend/api/api/.gitignore index 17288e4c..3c45979c 100644 --- a/backend/api/api/.gitignore +++ b/backend/api/api/.gitignore @@ -4,6 +4,7 @@ ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore ##Ignore contents for UploadedFiles Folder +PredictorFiles/* UploadedFiles/* !UploadedFiles/Igrannonica TempFiles/* diff --git a/backend/api/api/Controllers/DatasetController.cs b/backend/api/api/Controllers/DatasetController.cs index add85aba..0a9fe0bd 100644 --- a/backend/api/api/Controllers/DatasetController.cs +++ b/backend/api/api/Controllers/DatasetController.cs @@ -25,22 +25,23 @@ namespace api.Controllers _fileService = fileService; jwtToken = Token; } - public string getUsername() + + public string getUserId() { - string username; + string userId; var header = Request.Headers[HeaderNames.Authorization]; if (AuthenticationHeaderValue.TryParse(header, out var headerValue)) { var scheme = headerValue.Scheme; var parameter = headerValue.Parameter; - username = jwtToken.TokenToUsername(parameter); - if (username == null) + userId = jwtToken.TokenToId(parameter); + if (userId == null) return null; } else return null; - return username; + return userId; } // GET: api/<DatasetController>/mydatasets @@ -48,17 +49,17 @@ namespace api.Controllers [Authorize(Roles = "User,Guest")] public ActionResult<List<Dataset>> Get() { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); - if (username == "") + if (userId == "") return _datasetService.GetGuestDatasets(); //ako bude trebao ID, samo iz baze uzeti - return _datasetService.GetMyDatasets(username); + return _datasetService.GetMyDatasets(userId); } // GET: api/<DatasetController>/datesort/{ascdsc}/{latest} @@ -69,12 +70,12 @@ namespace api.Controllers [Authorize(Roles = "User")] public ActionResult<List<Dataset>> SortDatasets(bool ascdsc, int latest) { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); - List<Dataset> lista = _datasetService.SortDatasets(username, ascdsc, latest); + List<Dataset> lista = _datasetService.SortDatasets(userId, ascdsc, latest); if (latest == 0) return lista; @@ -100,14 +101,7 @@ namespace api.Controllers [Authorize(Roles = "User")] public ActionResult<List<Dataset>> Search(string name) { - string username = getUsername(); - - if (username == null) - return BadRequest(); - - //ako bude trebao ID, samo iz baze uzeti - - return _datasetService.SearchDatasets(name, username); + return _datasetService.SearchDatasets(name); } @@ -117,12 +111,12 @@ namespace api.Controllers [Authorize(Roles = "User")] public ActionResult<Dataset> Get(string name) { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); - var dataset = _datasetService.GetOneDataset(username, name); + var dataset = _datasetService.GetOneDataset(userId, name); if (dataset == null) return NotFound($"Dataset with name = {name} not found or dataset is not public or not preprocessed"); @@ -134,25 +128,15 @@ namespace api.Controllers [Authorize(Roles = "User,Guest")] public async Task<ActionResult<Dataset>> Post([FromBody] Dataset dataset) { - string uploaderId; - var header = Request.Headers[HeaderNames.Authorization]; - if (AuthenticationHeaderValue.TryParse(header, out var headerValue)) - { - var scheme = headerValue.Scheme; - var parameter = headerValue.Parameter; - uploaderId = jwtToken.TokenToId(parameter); - if (uploaderId == null) - return null; - } - else - return BadRequest(); + string uploaderId = getUserId(); + //da li ce preko tokena da se ubaci username ili front salje //dataset.username = usernameToken; //username = "" ako je GUEST DODAO - var existingDataset = _datasetService.GetOneDataset(dataset.username, dataset.name); + var existingDataset = _datasetService.GetOneDataset(dataset.uploaderId, dataset.name); if (existingDataset != null) - return NotFound($"Dateset with name = {dataset.name} exisits"); + return NotFound($"Dataset with this name already exists"); else { FileModel fileModel = _fileService.getFile(dataset.fileId); @@ -169,20 +153,20 @@ namespace api.Controllers [Authorize(Roles = "User")] public ActionResult Put(string name, [FromBody] Dataset dataset) { - string username = getUsername(); + string uploaderId = getUserId(); - if (username == null) + if (uploaderId == null) return BadRequest(); - var existingDataset = _datasetService.GetOneDataset(username, name); + var existingDataset = _datasetService.GetOneDataset(uploaderId, name); //ne mora da se proverava if (existingDataset == null) - return NotFound($"Dataset with name = {name} or user with username = {username} not found"); + return NotFound($"Dataset with name = {name} or user with ID = {uploaderId} not found"); dataset.lastUpdated = DateTime.UtcNow; - _datasetService.Update(username, name, dataset); + _datasetService.Update(uploaderId, name, dataset); return Ok($"Dataset with name = {name} updated"); } @@ -192,17 +176,17 @@ namespace api.Controllers [Authorize(Roles = "User")] public ActionResult Delete(string name) { - string username = getUsername(); + string uploaderId = getUserId(); - if (username == null) + if (uploaderId == null) return BadRequest(); - var dataset = _datasetService.GetOneDataset(username, name); + var dataset = _datasetService.GetOneDataset(uploaderId, name); if (dataset == null) - return NotFound($"Dataset with name = {name} or user with username = {username} not found"); + return NotFound($"Dataset with name = {name} or user with ID = {uploaderId} not found"); - _datasetService.Delete(dataset.username, dataset.name); + _datasetService.Delete(dataset.uploaderId, dataset.name); return Ok($"Dataset with name = {name} deleted"); diff --git a/backend/api/api/Controllers/FileController.cs b/backend/api/api/Controllers/FileController.cs index b9b31500..68d2ebed 100644 --- a/backend/api/api/Controllers/FileController.cs +++ b/backend/api/api/Controllers/FileController.cs @@ -44,27 +44,13 @@ namespace api.Controllers } [HttpPost("h5")] - [Authorize(Roles = "User,Guest")] - public async Task<ActionResult<string>> H5Upload([FromForm] IFormFile file) + public async Task<ActionResult<string>> H5Upload([FromForm] IFormFile file,[FromForm] string uploaderId) { //get username from jwtToken - string folderName; - - string uploaderId = getUserId(); + string folderName="PredictorFiles"; - if (uploaderId == null) - return BadRequest(); - - if (uploaderId == "") - { - folderName = "TempFiles"; - } - else - { - folderName = "UploadedFiles"; - } //Check filetype @@ -75,7 +61,7 @@ namespace api.Controllers { return BadRequest("Wrong file type"); } - var folderPath = Path.Combine(Directory.GetCurrentDirectory(), folderName, uploaderId); + var folderPath = Path.Combine(Directory.GetCurrentDirectory(), folderName,uploaderId); //Check Directory if (!Directory.Exists(folderPath)) { @@ -98,14 +84,14 @@ namespace api.Controllers await file.CopyToAsync(stream); } FileModel fileModel = new FileModel(); - fileModel.type = "h5"; + fileModel.type = ".h5"; fileModel.path = fullPath; fileModel.uploaderId = uploaderId; fileModel.date = DateTime.Now.ToUniversalTime(); fileModel = _fileservice.Create(fileModel); - return Ok(fileModel); + return Ok(fileModel._id); } [HttpGet("csvread/{hasHeader}/{fileId}")] diff --git a/backend/api/api/Controllers/ModelController.cs b/backend/api/api/Controllers/ModelController.cs index c31b24e1..fb30a7a2 100644 --- a/backend/api/api/Controllers/ModelController.cs +++ b/backend/api/api/Controllers/ModelController.cs @@ -56,23 +56,6 @@ namespace api.Controllers return uploaderId; } - public string getUsername() - { - string username; - var header = Request.Headers[HeaderNames.Authorization]; - if (AuthenticationHeaderValue.TryParse(header, out var headerValue)) - { - var scheme = headerValue.Scheme; - var parameter = headerValue.Parameter; - username = jwtToken.TokenToUsername(parameter); - if (username == null) - return null; - } - else - return null; - - return username; - } [HttpPost("trainModel")] [Authorize(Roles = "User,Guest")] @@ -81,28 +64,29 @@ namespace api.Controllers string experimentId = trainModelObject.ExperimentId; string modelId = trainModelObject.ModelId; - string uploaderId = getUserId(); + string userId = getUserId(); - if (uploaderId == null) + if (userId == null) return BadRequest(); var experiment=_experimentService.Get(experimentId); var dataset = _datasetService.GetOneDataset(experiment.datasetId); - var filepath = _fileService.GetFilePath(dataset.fileId, uploaderId); + var filepath = _fileService.GetFilePath(dataset.fileId, userId); var model = _modelService.GetOneModel(modelId); - _mlService.TrainModel(model,experiment,filepath,dataset,uploaderId);//To do Obavestiti korisnika kada se model istrenira + _mlService.TrainModel(model,experiment,filepath,dataset, userId);//To do Obavestiti korisnika kada se model istrenira return Ok(); } [HttpPost("epoch")] + [ServiceFilter(typeof(MlApiCheckActionFilter))] public async Task<ActionResult<string>> Epoch([FromBody] Epoch info) { var model=_modelService.GetOneModel(info.ModelId); - var user = _userService.GetUserByUsername(model.username); + var user = _userService.GetUserById(model.uploaderId); if (ChatHub.CheckUser(user._id)) - await _ichat.Clients.Client(ChatHub.Users[user._id]).SendAsync("NotifyEpoch",info.ModelId,info.Stat,model.epochs); + await _ichat.Clients.Client(ChatHub.Users[user._id]).SendAsync("NotifyEpoch",model.name,info.ModelId,info.Stat,model.epochs,info.EpochNum); return Ok(); } @@ -117,12 +101,30 @@ namespace api.Controllers [Authorize(Roles = "User")] public ActionResult<List<Model>> Get() { - string username = getUsername(); - - if (username == null) + string uploaderId = getUserId(); + + if (uploaderId == null) return BadRequest(); - return _modelService.GetMyModels(username); + return _modelService.GetMyModels(uploaderId); + } + + // GET: api/<ModelController>/mymodels + [HttpGet("mymodelsbytype/{problemtype}")] + [Authorize(Roles = "User")] + public ActionResult<List<Model>> GetMyModelsByType(string problemType) + { + string uploaderId = getUserId(); + + if (uploaderId == null) + return BadRequest(); + + List<Model> modeli = _modelService.GetMyModelsByType(uploaderId, problemType); + + if (modeli == null) + return NoContent(); + else + return modeli; } // vraca svoj model prema nekom imenu @@ -131,12 +133,12 @@ namespace api.Controllers [Authorize(Roles = "User")] public ActionResult<Model> Get(string name) { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); - var model = _modelService.GetOneModel(username, name); + var model = _modelService.GetOneModel(userId, name); if (model == null) return NotFound($"Model with name = {name} not found"); @@ -155,14 +157,14 @@ namespace api.Controllers [Authorize(Roles = "User")] public ActionResult<List<Model>> GetLatestModels(int latest) { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); //ako bude trebao ID, samo iz baze uzeti - List<Model> lista = _modelService.GetLatestModels(username); + List<Model> lista = _modelService.GetLatestModels(userId); List<Model> novaLista = new List<Model>(); @@ -185,7 +187,7 @@ namespace api.Controllers /*if (_modelService.CheckHyperparameters(1, model.hiddenLayerNeurons, model.hiddenLayers, model.outputNeurons) == false) return BadRequest("Bad parameters!");*/ - var existingModel = _modelService.GetOneModel(model.username, model.name); + var existingModel = _modelService.GetOneModel(model.uploaderId, model.name); if (existingModel != null && !overwrite) return NotFound($"Model with name = {model.name} exisits"); @@ -209,18 +211,18 @@ namespace api.Controllers [Authorize(Roles = "User")] public ActionResult Put(string name, [FromBody] Model model) { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); - var existingModel = _modelService.GetOneModel(username, name); + var existingModel = _modelService.GetOneModel(userId, name); if (existingModel == null) - return NotFound($"Model with name = {name} or user with username = {username} not found"); + return NotFound($"Model with name = {name} or user with ID = {userId} not found"); - _modelService.Update(username, name, model); + _modelService.Update(userId, name, model); return NoContent(); } @@ -229,17 +231,17 @@ namespace api.Controllers [Authorize(Roles = "User")] public ActionResult Delete(string name) { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); - var model = _modelService.GetOneModel(username, name); + var model = _modelService.GetOneModel(userId, name); if (model == null) - return NotFound($"Model with name = {name} or user with username = {username} not found"); + return NotFound($"Model with name = {name} or user with ID = {userId} not found"); - _modelService.Delete(model.username, model.name); + _modelService.Delete(model.uploaderId, model.name); return Ok($"Model with name = {name} deleted"); @@ -256,6 +258,7 @@ namespace api.Controllers public class Epoch { public string ModelId { get; set; } + public int EpochNum { get; set; } public string Stat { get; set; } } } diff --git a/backend/api/api/Controllers/PredictorController.cs b/backend/api/api/Controllers/PredictorController.cs index 233ea401..dd5aa5fd 100644 --- a/backend/api/api/Controllers/PredictorController.cs +++ b/backend/api/api/Controllers/PredictorController.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; using System.Net.Http.Headers; using System.Diagnostics; +using Microsoft.AspNetCore.SignalR; namespace api.Controllers { @@ -15,30 +16,38 @@ namespace api.Controllers private readonly IPredictorService _predictorService; private IJwtToken jwtToken; private readonly IMlConnectionService _mlConnectionService; + private readonly IExperimentService _experimentService; + private readonly IUserService _userService; + private readonly IHubContext<ChatHub> _ichat; + private readonly IModelService _modelService; - public PredictorController(IPredictorService predictorService, IConfiguration configuration, IJwtToken Token, IMlConnectionService mlConnectionService) + public PredictorController(IPredictorService predictorService, IConfiguration configuration, IJwtToken Token, IMlConnectionService mlConnectionService, IExperimentService experimentService,IUserService userService, IHubContext<ChatHub> ichat,IModelService modelService) { _predictorService = predictorService; jwtToken = Token; _mlConnectionService = mlConnectionService; + _experimentService = experimentService; + _userService = userService; + _ichat = ichat; + _modelService = modelService; } - public string getUsername() + public string getUserId() { - string username; + string uploaderId; var header = Request.Headers[HeaderNames.Authorization]; if (AuthenticationHeaderValue.TryParse(header, out var headerValue)) { var scheme = headerValue.Scheme; var parameter = headerValue.Parameter; - username = jwtToken.TokenToUsername(parameter); - if (username == null) + uploaderId = jwtToken.TokenToId(parameter); + if (uploaderId == null) return null; } else return null; - return username; + return uploaderId; } // GET: api/<PredictorController>/mypredictors @@ -46,12 +55,12 @@ namespace api.Controllers [Authorize(Roles = "User")] public ActionResult<List<Predictor>> Get() { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); - return _predictorService.GetMyPredictors(username); + return _predictorService.GetMyPredictors(userId); } // GET: api/<PredictorController>/publicpredictors @@ -63,29 +72,30 @@ namespace api.Controllers //SEARCH za predictore (public ili private sa ovim imenom ) // GET api/<PredictorController>/search/{name} - [HttpGet("search/{name}")] - [Authorize(Roles = "User")] - public ActionResult<List<Predictor>> Search(string name) - { - string username = getUsername(); + + //[HttpGet("search/{name}")] + //[Authorize(Roles = "User")] + //public ActionResult<List<Predictor>> Search(string name) + //{ + // string username = getUsername(); - if (username == null) - return BadRequest(); + // if (username == null) + // return BadRequest(); - return _predictorService.SearchPredictors(name, username); - } + // return _predictorService.SearchPredictors(name, username); + //} // GET api/<PredictorController>/getpredictor/{name} [HttpGet("getpredictor/{id}")] [Authorize(Roles = "User,Guest")] public ActionResult<Predictor> GetPredictor(string id) { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); - Predictor predictor = _predictorService.GetPredictor(username, id); + Predictor predictor = _predictorService.GetPredictor(userId, id); if (predictor == null) return NotFound($"Predictor with id = {id} not found"); @@ -96,17 +106,18 @@ namespace api.Controllers // GET api/<PredictorController>/{name} [HttpGet("{name}")] [Authorize(Roles = "User")] - public ActionResult<Predictor> Get(string name) + public ActionResult<Predictor> Get(string id) { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); - var predictor = _predictorService.GetOnePredictor(username, name); + //treba userId da se salje GetOnePredictor + var predictor = _predictorService.GetOnePredictor(id); if (predictor == null) - return NotFound($"Predictor with name = {name} not found or predictor is not public"); + return NotFound($"Predictor with id = {id} not found or predictor is not public"); return predictor; } @@ -120,12 +131,12 @@ namespace api.Controllers [Authorize(Roles = "User")] public ActionResult<List<Predictor>> SortPredictors(bool ascdsc, int latest) { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); - List<Predictor> lista = _predictorService.SortPredictors(username, ascdsc, latest); + List<Predictor> lista = _predictorService.SortPredictors(userId, ascdsc, latest); if(latest == 0) return lista; @@ -142,77 +153,83 @@ namespace api.Controllers // POST api/<PredictorController>/add [HttpPost("add")] - [Authorize(Roles = "User")] - public ActionResult<Predictor> Post([FromBody] Predictor predictor) + public async Task<ActionResult<Predictor>> Post([FromBody] Predictor predictor) { - var existingPredictor = _predictorService.GetOnePredictor(predictor.username, predictor.name); - - if (existingPredictor != null) - return NotFound($"Predictor with name = {predictor.name} exisits"); - else - { - _predictorService.Create(predictor); - - return CreatedAtAction(nameof(Get), new { id = predictor._id }, predictor); - } + var user=_userService.GetUserById(predictor.uploaderId); + predictor.dateCreated = DateTime.Now.ToUniversalTime(); + var model = _modelService.GetOneModel(predictor.modelId); + if (model == null || user==null) + return BadRequest("Model not found or user doesnt exist"); + _predictorService.Create(predictor); + if (ChatHub.CheckUser(user._id)) + await _ichat.Clients.Client(ChatHub.Users[user._id]).SendAsync("NotifyPredictor", predictor._id,model.name); + return CreatedAtAction(nameof(Get), new { id = predictor._id }, predictor); + } // POST api/<PredictorController>/usepredictor {predictor,inputs} [HttpPost("usepredictor/{id}")] [Authorize(Roles = "User,Guest")] - public ActionResult UsePredictor(String id, [FromBody] PredictorColumns[] inputs) + public async Task<ActionResult> UsePredictor(String id, [FromBody] PredictorColumns[] inputs) { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); - Predictor predictor = _predictorService.GetPredictor(username, id); - + Predictor predictor = _predictorService.GetPredictor(userId, id); + + Experiment e = _experimentService.Get(predictor.experimentId); + + string result = await _mlConnectionService.Predict(predictor, e, inputs); + + //salji ml + + /* foreach(PredictorColumns i in inputs) - Debug.WriteLine(i.value.ToString()); - return NoContent(); + Debug.WriteLine(i.value.ToString());*/ + return Ok(result); } // PUT api/<PredictorController>/{name} [HttpPut("{name}")] [Authorize(Roles = "User")] - public ActionResult Put(string name, [FromBody] Predictor predictor) + public ActionResult Put(string id, [FromBody] Predictor predictor) { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); - var existingPredictor = _predictorService.GetOnePredictor(username, name); + var existingPredictor = _predictorService.GetOnePredictor(id); //ne mora da se proverava if (existingPredictor == null) - return NotFound($"Predictor with name = {name} or user with username = {username} not found"); + return NotFound($"Predictor with id = {id} or user with ID = {userId} not found"); - _predictorService.Update(username, name, predictor); + _predictorService.Update(id, predictor); - return Ok($"Predictor with name = {name} updated"); + return Ok($"Predictor with id = {id} updated"); } // DELETE api/<PredictorController>/name - [HttpDelete("{name}")] + [HttpDelete("{id}")] [Authorize(Roles = "User")] - public ActionResult Delete(string name) + public ActionResult Delete(string id) { - string username = getUsername(); + string userId = getUserId(); - if (username == null) + if (userId == null) return BadRequest(); - var predictor = _predictorService.GetOnePredictor(username, name); + var predictor = _predictorService.GetOnePredictor(id); if (predictor == null) - return NotFound($"Predictor with name = {name} or user with username = {username} not found"); + return NotFound($"Predictor with id = {id} or user with ID = {userId} not found"); - _predictorService.Delete(predictor.username, predictor.name); + _predictorService.Delete(id, userId); - return Ok($"Predictor with name = {name} deleted"); + return Ok($"Predictor with id = {id} deleted"); } } diff --git a/backend/api/api/Models/ColumnEncoding.cs b/backend/api/api/Models/ColumnEncoding.cs new file mode 100644 index 00000000..2a2fce8b --- /dev/null +++ b/backend/api/api/Models/ColumnEncoding.cs @@ -0,0 +1,14 @@ +namespace api.Models +{ + public class ColumnEncoding + { + public ColumnEncoding(string columnName, string encoding) + { + this.columnName = columnName; + this.encoding = encoding; + } + + public string columnName { get; set; } + public string encoding { get; set; } + } +} diff --git a/backend/api/api/Models/ColumnInfo.cs b/backend/api/api/Models/ColumnInfo.cs index 99418732..04450fef 100644 --- a/backend/api/api/Models/ColumnInfo.cs +++ b/backend/api/api/Models/ColumnInfo.cs @@ -2,6 +2,20 @@ { public class ColumnInfo { + public ColumnInfo() { } + + public ColumnInfo(string columnName, bool isNumber, int numNulls, float mean, float min, float max, float median, string[] uniqueValues) + { + this.columnName = columnName; + this.isNumber = isNumber; + this.numNulls = numNulls; + this.mean = mean; + this.min = min; + this.max = max; + this.median = median; + this.uniqueValues = uniqueValues; + } + public string columnName { get; set; } public bool isNumber { get; set; } public int numNulls { get; set; } diff --git a/backend/api/api/Models/Dataset.cs b/backend/api/api/Models/Dataset.cs index 47814449..cc7185f0 100644 --- a/backend/api/api/Models/Dataset.cs +++ b/backend/api/api/Models/Dataset.cs @@ -7,7 +7,7 @@ namespace api.Models public class Dataset { public Dataset() { } - public string username { get; set; } + public string uploaderId { get; set; } [BsonId] [BsonRepresentation(BsonType.ObjectId)]//mongo data type to .net diff --git a/backend/api/api/Models/Experiment.cs b/backend/api/api/Models/Experiment.cs index bf029116..f7bec083 100644 --- a/backend/api/api/Models/Experiment.cs +++ b/backend/api/api/Models/Experiment.cs @@ -10,7 +10,7 @@ namespace api.Models public string _id { get; set; } public string name { get; set; } public string description { get; set; } - public string encoding { get; set; } + public string type { get; set; } public List<string> ModelIds { get; set; } public string datasetId { get; set; } public string uploaderId { get; set; } @@ -21,6 +21,7 @@ namespace api.Models public float randomTestSetDistribution { get; set; } public string nullValues { get; set; } public NullValues[] nullValuesReplacers { get; set; } + public ColumnEncoding[] encodings { get; set; } } } diff --git a/backend/api/api/Models/Model.cs b/backend/api/api/Models/Model.cs index 72ee093b..a9dbfbdd 100644 --- a/backend/api/api/Models/Model.cs +++ b/backend/api/api/Models/Model.cs @@ -9,7 +9,7 @@ namespace api.Models [BsonId] [BsonRepresentation(BsonType.ObjectId)]//mongo data type to .net public string _id { get; set; } - public string username { get; set; } + public string uploaderId { get; set; } public string name { get; set; } diff --git a/backend/api/api/Models/Predictor.cs b/backend/api/api/Models/Predictor.cs index b1d6444b..342c5b5d 100644 --- a/backend/api/api/Models/Predictor.cs +++ b/backend/api/api/Models/Predictor.cs @@ -9,25 +9,37 @@ namespace api.Models [BsonId] [BsonRepresentation(BsonType.ObjectId)]//mongo data type to .net public string _id { get; set; } - public string username { get; set; } - public string name { get; set; } - public string description { get; set; } + public string uploaderId { get; set; } + //public string name { get; set; } + //public string description { get; set; } public string[] inputs { get; set; } public string output { get; set; } public bool isPublic { get; set; } public bool accessibleByLink { get; set; } public DateTime dateCreated { get; set; } public string experimentId { get; set; } - + public string modelId { get; set; } + public string h5FileId { get; set; } + public Metric[] metrics { get; set; } } + + public class Metric + { + string Name { get; set; } + string JsonValue { get; set; } + + } } -/* +/** +* Paste one or more documents here + { - "_id" : "", - "username" : "ivan123", - "name" : "Neki prediktor", - "description" : "Neki opis prediktora koji je unet tamo", + "_id": { + "$oid": "625dc348b7856ace8a6f8702" + + }, + "uploaderId" : "6242ea59486c664208d4255c", "inputs": ["proba", "proba2", "proba3" @@ -36,6 +48,8 @@ namespace api.Models "isPublic" : true, "accessibleByLink" : true, "dateCreated" : "2022-04-11T20:33:26.937+00:00", - "experimentId" : "Neki id eksperimenta" -} -*/
\ No newline at end of file + "experimentId" : "Neki id eksperimenta", + "modelId" : "Neki id eksperimenta", + "h5FileId" : "Neki id eksperimenta", + "metrics" : [{ }] +}*/
\ No newline at end of file diff --git a/backend/api/api/Program.cs b/backend/api/api/Program.cs index 576093fe..56abc016 100644 --- a/backend/api/api/Program.cs +++ b/backend/api/api/Program.cs @@ -39,7 +39,14 @@ builder.Services.AddScoped<IExperimentService, ExperimentService>(); builder.Services.AddHostedService<TempFileService>(); builder.Services.AddHostedService<FillAnEmptyDb>(); - +//Ml Api Ip Filter +builder.Services.AddScoped<MlApiCheckActionFilter>(container => +{ + var loggerFactory = container.GetRequiredService<ILoggerFactory>(); + var logger=loggerFactory.CreateLogger<MlApiCheckActionFilter>(); + var MlIp = builder.Configuration.GetValue<string>("AppSettings:MlIp"); + return new MlApiCheckActionFilter(MlIp, logger); +}); @@ -64,11 +71,12 @@ builder.Services.Configure<FormOptions>(x => builder.Services.AddSignalR(); builder.Services.AddControllers(); - +string frontApi = builder.Configuration.GetValue<string>("AppSettings:FrontApi"); +string mlApi = builder.Configuration.GetValue<string>("AppSettings:MlApi"); builder.Services.AddCors(options => { options.AddPolicy("CorsPolicy", builder => builder - .WithOrigins("http://localhost:4200") + .WithOrigins(frontApi, mlApi) .AllowAnyMethod() .AllowAnyHeader() .AllowCredentials()); diff --git a/backend/api/api/Properties/launchSettings.json b/backend/api/api/Properties/launchSettings.json index 5c9dd2a8..f6c3fe8b 100644 --- a/backend/api/api/Properties/launchSettings.json +++ b/backend/api/api/Properties/launchSettings.json @@ -1,31 +1,23 @@ { - "$schema": "https://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:61918", - "sslPort": 0 - } - }, - "profiles": { - "api": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": false, - "launchUrl": "", - "applicationUrl": "http://localhost:5283", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:61918", + "sslPort": 0 + } }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": false, - "launchUrl": "", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } + "profiles": { + "api": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "launchUrl": "", + "applicationUrl": "http://localhost:5283", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } } - } } diff --git a/backend/api/api/Services/DatasetService.cs b/backend/api/api/Services/DatasetService.cs index 176ab424..f260a1ba 100644 --- a/backend/api/api/Services/DatasetService.cs +++ b/backend/api/api/Services/DatasetService.cs @@ -14,7 +14,7 @@ namespace api.Services _dataset = database.GetCollection<Dataset>(settings.DatasetCollectionName); } - public List<Dataset> SearchDatasets(string name, string username) + public List<Dataset> SearchDatasets(string name) { return _dataset.Find(dataset => dataset.name == name && dataset.isPublic == true && dataset.isPreProcess).ToList(); } @@ -27,27 +27,27 @@ namespace api.Services } //brisanje odredjenog name-a - public void Delete(string username, string name) + public void Delete(string userId, string id) { - _dataset.DeleteOne(dataset => (dataset.username == username && dataset.name == name)); + _dataset.DeleteOne(dataset => (dataset.uploaderId == userId && dataset._id == id)); } - public List<Dataset> GetMyDatasets(string username) + public List<Dataset> GetMyDatasets(string userId) { - return _dataset.Find(dataset => dataset.username == username && dataset.isPreProcess).ToList(); + return _dataset.Find(dataset => dataset.uploaderId == userId && dataset.isPreProcess).ToList(); } public List<Dataset> GetGuestDatasets() { //Join Igranonica public datasetove sa svim temp uploadanim datasetovima - List<Dataset> datasets= _dataset.Find(dataset => dataset.username == "Igrannonica" && dataset.isPublic == true && dataset.isPreProcess).ToList(); - datasets.AddRange(_dataset.Find(dataset => dataset.username == "" && dataset.isPreProcess).ToList()); + List<Dataset> datasets= _dataset.Find(dataset => dataset.uploaderId == "Igrannonica" && dataset.isPublic == true && dataset.isPreProcess).ToList(); + datasets.AddRange(_dataset.Find(dataset => dataset.uploaderId == "" && dataset.isPreProcess).ToList()); return datasets; } //poslednji datasetovi - public List<Dataset> SortDatasets(string username, bool ascdsc, int latest) + public List<Dataset> SortDatasets(string userId, bool ascdsc, int latest) { - List<Dataset> list = _dataset.Find(dataset => dataset.username == username && dataset.isPreProcess).ToList(); + List<Dataset> list = _dataset.Find(dataset => dataset.uploaderId == userId && dataset.isPreProcess).ToList(); if(ascdsc) list = list.OrderBy(dataset => dataset.lastUpdated).ToList(); @@ -62,9 +62,9 @@ namespace api.Services return _dataset.Find(dataset => dataset.isPublic == true && dataset.isPreProcess).ToList(); } - public Dataset GetOneDataset(string username, string name) + public Dataset GetOneDataset(string userId, string name) { - return _dataset.Find(dataset => dataset.username == username && dataset.name == name && dataset.isPreProcess).FirstOrDefault(); + return _dataset.Find(dataset => dataset.uploaderId == userId && dataset.name == name && dataset.isPreProcess).FirstOrDefault(); } //odraditi za pretragu getOne @@ -74,9 +74,9 @@ namespace api.Services } //ako je potrebno da se zameni name ili ekstenzija - public void Update(string username, string name, Dataset dataset ) + public void Update(string userId, string id, Dataset dataset ) { - _dataset.ReplaceOne(dataset => dataset.username == username && dataset.name == name, dataset); + _dataset.ReplaceOne(dataset => dataset.uploaderId == userId && dataset._id == id, dataset); } public void Update(Dataset dataset) { @@ -85,7 +85,7 @@ namespace api.Services public string GetDatasetId(string fileId) { - Dataset dataset = _dataset.Find(dataset => dataset.fileId == fileId && dataset.username == "Igrannonica").FirstOrDefault(); + Dataset dataset = _dataset.Find(dataset => dataset.fileId == fileId && dataset.uploaderId == "Igrannonica").FirstOrDefault(); return dataset._id; } diff --git a/backend/api/api/Services/FillAnEmptyDb.cs b/backend/api/api/Services/FillAnEmptyDb.cs index 33c1ada6..6d5683bd 100644 --- a/backend/api/api/Services/FillAnEmptyDb.cs +++ b/backend/api/api/Services/FillAnEmptyDb.cs @@ -1,5 +1,6 @@ using api.Interfaces; using api.Models; +using Microsoft.AspNetCore.SignalR; using MongoDB.Driver; namespace api.Services @@ -32,10 +33,10 @@ namespace api.Services if (_fileService.CheckDb()) { - /* + FileModel file = new FileModel(); - string folderName = "UploadedFiles/Igrannonica"; + string folderName = "UploadedFiles"; var folderPath = Path.Combine(Directory.GetCurrentDirectory(), folderName, "Igrannonica"); var fullPath = Path.Combine(folderPath, "titanic.csv"); @@ -51,9 +52,9 @@ namespace api.Services Dataset dataset = new Dataset(); dataset._id = ""; - dataset.username = "Igrannonica"; + dataset.uploaderId = "Igrannonica"; dataset.name = "Titanik dataset"; - dataset.description = "Opis dataseta 1"; + dataset.description = "Titanik dataset"; dataset.header = new string[] { "PassengerId", "Survived", "Pclass", "Name", "Sex", "Age", "SibSp", "Parch", "Ticket", "Fare", "Cabin", "Embarked" }; dataset.fileId = _fileService.GetFileId(fullPath); dataset.extension = ".csv"; @@ -64,9 +65,25 @@ namespace api.Services dataset.delimiter = ""; dataset.hasHeader = true; dataset.columnInfo = new ColumnInfo[] { }; + dataset.columnInfo = new[] + { + new ColumnInfo( "PassengerId", true, 0, 446, 1, 891, 446, new string[]{ }), + new ColumnInfo( "Survived", true, 0, 0.38383838534355164f, 0, 1, 0, new string[]{ }), + new ColumnInfo( "Pclass", true, 0, 2.3086419105529785f, 1, 3, 3, new string[]{ }), + new ColumnInfo( "Name", false, 0, 0, 0, 0, 0, new string[]{"Braund, Mr. Owen Harris", "Boulos, Mr. Hanna", "Frolicher-Stehli, Mr. Maxmillian", "Gilinski, Mr. Eliezer", "Murdlin, Mr. Joseph", "Rintamaki, Mr. Matti", "Stephenson, Mrs. Walter Bertram (Martha Eustis)", "Elsbury, Mr. William James", "Bourke, Miss. Mary", "Chapman, Mr. John Henry"}), + new ColumnInfo( "Sex", false, 0, 0, 0, 0, 0, new string[]{ "male", "female" }), + new ColumnInfo( "Age", true, 177, 29.69911766052246f, 0.41999998688697815f, 80, 28, new string[]{ }), + new ColumnInfo( "SibSp", true, 0, 0.523007869720459f, 0, 8, 0, new string[]{ }), + new ColumnInfo( "Parch", true, 0, 0.3815937042236328f, 0, 6, 0, new string[]{ }), + new ColumnInfo( "Ticket", false, 0, 0, 0, 0, 0, new string[]{ "347082", "CA. 2343", "1601", "3101295", "CA 2144", "347088", "S.O.C. 14879", "382652", "LINE", "PC 17757" }), + new ColumnInfo( "Fare", true, 0, 32.20420837402344f, 0, 512.3292236328125f, 14.45419979095459f, new string[]{ }), + new ColumnInfo( "Cabin", false, 687, 0, 0, 0, 0, new string[]{ "B96 B98", "G6", "C23 C25 C27", "C22 C26", "F33", "F2", "E101", "D", "C78", "C93" }), + new ColumnInfo( "Embarked", false, 2, 0.3815937042236328f, 0, 6, 0, new string[]{ "S", "C", "Q" }), + }; + dataset.rowCount = 891; dataset.nullCols = 3; dataset.nullRows = 708; - dataset.isPreProcess = false; + dataset.isPreProcess = true; _datasetService.Create(dataset); @@ -74,25 +91,22 @@ namespace api.Services Model model = new Model(); model._id = ""; - model.username = "Igrannonica"; - model.name = "Titanik model"; - model.description = "Opis modela 1"; + model.uploaderId = "Igrannonica"; + model.name = "Model Titanik"; + model.description = "Model Titanik"; model.dateCreated = DateTime.Now; model.lastUpdated = DateTime.Now; - model.experimentId = ""; - model.type = "regresioni"; - model.encoding = "label"; + model.type = "binarni-klasifikacioni"; model.optimizer = "Adam"; model.lossFunction = "mean_squared_error"; - model.hiddenLayerNeurons = 1; - model.hiddenLayers = 1; - model.batchSize = 5; + model.hiddenLayerNeurons = 3; + model.hiddenLayers = 5; + model.batchSize = 8; model.outputNeurons = 0; - model.hiddenLayerActivationFunctions = new string[] { "sigmoid" }; + model.hiddenLayerActivationFunctions = new string[] { "relu", "relu", "relu", "relu", "relu" }; model.outputLayerActivationFunction = "sigmoid"; model.metrics = new string[] { }; model.epochs = 5; - model.isTrained = false; _modelService.Create(model); @@ -100,18 +114,35 @@ namespace api.Services Experiment experiment = new Experiment(); experiment._id = ""; + experiment.name = "Eksperiment Titanik"; + experiment.description = "Binarno klasifikacioni, label"; + experiment.ModelIds = new string[] { }.ToList(); experiment.datasetId = _datasetService.GetDatasetId(dataset.fileId); experiment.uploaderId = "Igrannonica"; - experiment.inputColumns = new string[] { }; - experiment.outputColumn = ""; - experiment.randomOrder = false; - experiment.randomTestSet = false; - experiment.randomTestSetDistribution = 0; - experiment.nullValues = ""; + experiment.inputColumns = new string[] { "Embarked" }; + experiment.outputColumn = "Survived"; + experiment.randomOrder = true; + experiment.randomTestSet = true; + experiment.randomTestSetDistribution = 0.30000001192092896f; + experiment.nullValues = "delete_rows"; experiment.nullValuesReplacers = new NullValues[] { }; + experiment.encodings = new[] + { + new ColumnEncoding( "Survived", "label" ), + new ColumnEncoding("Embarked", "label" ) + }; _experimentService.Create(experiment); + var experiment1 = _experimentService.Get(experiment._id); + var dataset1 = _datasetService.GetOneDataset(experiment.datasetId); + var filepath1 = _fileService.GetFilePath(dataset.fileId, "Igrannonica"); + var model1 = _modelService.GetOneModel(model._id); + + + //_mlService.TrainModel(model1, experiment1, filepath1, dataset1, "Igrannonica"); + + /* Predictor predictor = new Predictor(); @@ -127,7 +158,7 @@ namespace api.Services predictor.experimentId = "0"; //izmeni experiment id - _predictorService.Create(predictor); + _predictorService.Create(predictor);*/ //-------------------------------------------------------------------- @@ -142,14 +173,13 @@ namespace api.Services _fileService.Create(file); - + dataset = new Dataset(); dataset._id = ""; - dataset.username = "Igrannonica"; + dataset.uploaderId = "Igrannonica"; dataset.name = "Diamonds dataset"; - dataset.description = "Opis dataseta 2"; - dataset.header = new string[] { "carat", "cut", "color", "clarity", "depth", "table", "price", "x", "y", "z" }; + dataset.description = "Diamonds dataset"; dataset.fileId = _fileService.GetFileId(fullPath); dataset.extension = ".csv"; dataset.isPublic = true; @@ -157,57 +187,91 @@ namespace api.Services dataset.dateCreated = DateTime.Now; dataset.lastUpdated = DateTime.Now; dataset.delimiter = ""; - dataset.hasHeader = true; - dataset.columnInfo = new ColumnInfo[] { }; + dataset.hasHeader = true; + dataset.columnInfo = new[] + { + new ColumnInfo( "Unnamed: 0", true, 0, 26969.5f, 0, 53939, 26969.5f, new string[]{ }), + new ColumnInfo( "carat", true, 0, 0.7979397773742676f, 0.20000000298023224f, 5.010000228881836f, 0.699999988079071f, new string[]{ }), + new ColumnInfo( "cut", false, 0, 0, 0, 0, 0, new string[]{ "Ideal", "Premium", "Very Good", "Good", "Fair" }), + new ColumnInfo( "color", false, 0, 0, 0, 0, 0, new string[]{"G", "E", "F", "H", "D", "I", "I", "J"}), + new ColumnInfo( "clarity", false, 0, 0, 0, 0, 0, new string[]{ "SI1", "VS2","SI2", "VS1", "VVS2", "VVS1", "IF", "I1" }), + new ColumnInfo( "depth", true, 0, 61.74940490722656f, 43, 79, 61.79999923706055f, new string[]{ }), + new ColumnInfo( "table", true, 0, 57.457183837890625f, 43, 95, 57, new string[]{ }), + new ColumnInfo( "price", true, 0, 3932.7998046875f, 326, 18823, 2401, new string[]{ }), + new ColumnInfo( "x", true, 0, 5.731157302856445f, 0, 10.739999771118164f, 5.699999809265137f, new string[]{ }), + new ColumnInfo( "y", true, 0, 5.73452615737915f, 0, 58.900001525878906f, 5.710000038146973f, new string[]{ }), + new ColumnInfo( "z", true, 0, 3.538733720779419f, 0, 31.799999237060547f, 3.5299999713897705f, new string[]{ }) + }; + dataset.rowCount = 53940; dataset.nullCols = 0; dataset.nullRows = 0; - dataset.isPreProcess = false; + dataset.isPreProcess = true; _datasetService.Create(dataset); - */ - /* + + model = new Model(); model._id = ""; - model.username = "Igrannonica"; - model.name = "Igrannonica model 2"; - model.description = "Opis modela 2"; + model.uploaderId = "Igrannonica"; + model.name = "Diamonds model"; + model.description = "Diamonds model"; model.dateCreated = DateTime.Now; model.lastUpdated = DateTime.Now; - model.experimentId = ""; - model.type = ""; - model.encoding = ""; - model.optimizer = ""; - model.lossFunction = ""; - model.hiddenLayerNeurons = 0; - model.hiddenLayers = 0; - model.batchSize = 0; + model.type = "regresioni"; + model.optimizer = "Adam"; + model.lossFunction = "mean_absolute_error"; + model.hiddenLayerNeurons = 2; + model.hiddenLayers = 4; + model.batchSize = 5; model.outputNeurons = 0; - model.hiddenLayerActivationFunctions = new string[] { "sigmoid" }; - model.outputLayerActivationFunction = ""; + model.hiddenLayerActivationFunctions = new string[] { "relu", "relu", "relu", "relu" }; + model.outputLayerActivationFunction = "relu"; model.metrics = new string[] { }; - model.epochs = 0; - model.isTrained = false; + model.epochs = 5; _modelService.Create(model); - + experiment = new Experiment(); experiment._id = ""; + experiment.name = "Diamonds eksperiment"; + experiment.description = "Diamonds eksperiment"; + experiment.ModelIds = new string[] { }.ToList(); experiment.datasetId = _datasetService.GetDatasetId(dataset.fileId); experiment.uploaderId = "Igrannonica"; - experiment.inputColumns = new string[] { }; - experiment.outputColumn = ""; - experiment.randomOrder = false; - experiment.randomTestSet = false; - experiment.randomTestSetDistribution = 0; - experiment.nullValues = ""; - experiment.nullValuesReplacers = new NullValues[] { }; - + experiment.inputColumns = new string[] { "Unnamed: 0", "carat", "cut", "color", "clarity", "depth", "table", "x", "y", "z" }; + experiment.outputColumn = "price"; + experiment.randomOrder = true; + experiment.randomTestSet = true; + experiment.randomTestSetDistribution = 0.30000001192092896f; + experiment.nullValues = "delete_rows"; + experiment.nullValuesReplacers = new NullValues[] { }; + experiment.encodings = new[] + { + new ColumnEncoding( "Unnamed: 0", "label" ), + new ColumnEncoding( "carat", "label" ), + new ColumnEncoding( "cut", "label" ), + new ColumnEncoding( "color", "label" ), + new ColumnEncoding( "clarity", "label" ), + new ColumnEncoding( "depth", "label" ), + new ColumnEncoding( "table", "label" ), + new ColumnEncoding( "price", "label" ), + new ColumnEncoding( "x", "label" ), + new ColumnEncoding( "y", "label" ), + new ColumnEncoding( "z", "label" ) + }; + _experimentService.Create(experiment); + experiment1 = _experimentService.Get(experiment._id); + dataset1 = _datasetService.GetOneDataset(experiment.datasetId); + filepath1 = _fileService.GetFilePath(dataset.fileId, "Igrannonica"); + model1 = _modelService.GetOneModel(model._id); + //_mlService.TrainModel(model1, experiment1, filepath1, dataset1, "Igrannonica"); + /* predictor = new Predictor(); @@ -224,12 +288,12 @@ namespace api.Services //izmeni experiment id _predictorService.Create(predictor); - + */ //-------------------------------------------------------------------- file = new FileModel(); - fullPath = Path.Combine(folderPath, "IMDB-Movie-Data.csv"); + fullPath = Path.Combine(folderPath, "iris.csv"); file._id = ""; file.type = ".csv"; file.uploaderId = "Igrannonica"; @@ -242,10 +306,9 @@ namespace api.Services dataset = new Dataset(); dataset._id = ""; - dataset.username = "Igrannonica"; - dataset.name = "Igrannonica dataset 3"; - dataset.description = "Opis dataseta 3"; - dataset.header = new string[] { "PassengerId", "Survived", "Pclass", "Name", "Sex", "Age", "SibSp", "Parch", "Ticket", "Fare", "Cabin", "Embarked" }; + dataset.uploaderId = "Igrannonica"; + dataset.name = "Iris dataset"; + dataset.description = "Iris dataset"; dataset.fileId = _fileService.GetFileId(fullPath); dataset.extension = ".csv"; dataset.isPublic = true; @@ -254,56 +317,77 @@ namespace api.Services dataset.lastUpdated = DateTime.Now; dataset.delimiter = ""; dataset.hasHeader = true; - dataset.columnInfo = new ColumnInfo[] { }; - dataset.nullCols = 0; + dataset.columnInfo = new[] + { + new ColumnInfo( "sepal_length", true, 0, 5.8433332443237305f, 4.300000190734863f, 7.900000095367432f, 5.800000190734863f, new string[]{ }), + new ColumnInfo( "sepal_width", true, 0, 3.053999900817871f, 2, 4.400000095367432f, 3, new string[]{ }), + new ColumnInfo( "petal_length", true, 0, 3.758666753768921f, 1, 6.900000095367432f, 4.349999904632568f, new string[]{ }), + new ColumnInfo( "petal_width", true, 0, 1.1986666917800903f, 0.10000000149011612f, 2.5f, 1.2999999523162842f, new string[]{}), + new ColumnInfo( "class", false, 0, 0, 0, 0, 0, new string[]{ "Iris-setosa", "Iris-versicolor", "Iris-virginica" }), + }; + dataset.nullCols = 150; dataset.nullRows = 0; - dataset.isPreProcess = false; + dataset.isPreProcess = true; _datasetService.Create(dataset); - + model = new Model(); model._id = ""; - model.username = "Igrannonica"; - model.name = "Igrannonica model 3"; - model.description = "Opis modela 3"; + model.uploaderId = "Igrannonica"; + model.name = "Model Iris"; + model.description = "Model Iris"; model.dateCreated = DateTime.Now; model.lastUpdated = DateTime.Now; - model.experimentId = ""; - model.type = ""; - model.encoding = ""; - model.optimizer = ""; - model.lossFunction = ""; - model.hiddenLayerNeurons = 0; - model.hiddenLayers = 0; - model.batchSize = 0; + model.type = "multi-klasifikacioni"; + model.optimizer = "Adam"; + model.lossFunction = "sparse_categorical_crossentropy"; + model.hiddenLayerNeurons = 3; + model.hiddenLayers = 3; + model.batchSize = 4; model.outputNeurons = 0; - model.hiddenLayerActivationFunctions = new string[] { "sigmoid" }; - model.outputLayerActivationFunction = ""; + model.hiddenLayerActivationFunctions = new string[] { "relu", "relu", "softmax" }; + model.outputLayerActivationFunction = "softmax"; model.metrics = new string[] { }; - model.epochs = 0; - model.isTrained = false; + model.epochs = 1; _modelService.Create(model); - + experiment = new Experiment(); experiment._id = ""; + experiment.name = "Iris eksperiment"; + experiment.description = "Iris eksperiment"; + experiment.ModelIds = new string[] { }.ToList(); experiment.datasetId = _datasetService.GetDatasetId(dataset.fileId); experiment.uploaderId = "Igrannonica"; - experiment.inputColumns = new string[] { }; - experiment.outputColumn = ""; - experiment.randomOrder = false; - experiment.randomTestSet = false; - experiment.randomTestSetDistribution = 0; - experiment.nullValues = ""; - experiment.nullValuesReplacers = new NullValues[] { }; + experiment.inputColumns = new string[] { "sepal_length", "sepal_width", "petal_length", "petal_width" }; + experiment.outputColumn = "class"; + experiment.randomOrder = true; + experiment.randomTestSet = true; + experiment.randomTestSetDistribution = 0.20000000298023224f; + experiment.nullValues = "delete_rows"; + experiment.nullValuesReplacers = new NullValues[] { }; + experiment.encodings = new[] + { + new ColumnEncoding( "sepal_length", "label" ), + new ColumnEncoding("sepal_width", "label" ), + new ColumnEncoding( "petal_length", "label" ), + new ColumnEncoding( "petal_width", "label" ), + new ColumnEncoding( "class", "label" ) + }; _experimentService.Create(experiment); + experiment1 = _experimentService.Get(experiment._id); + dataset1 = _datasetService.GetOneDataset(experiment.datasetId); + filepath1 = _fileService.GetFilePath(dataset.fileId, "Igrannonica"); + model1 = _modelService.GetOneModel(model._id); + //_mlService.TrainModel(model1, experiment1, filepath1, dataset1, "Igrannonica"); + /* predictor = new Predictor(); predictor._id = ""; diff --git a/backend/api/api/Services/IDatasetService.cs b/backend/api/api/Services/IDatasetService.cs index c6138943..f493a2ec 100644 --- a/backend/api/api/Services/IDatasetService.cs +++ b/backend/api/api/Services/IDatasetService.cs @@ -5,15 +5,15 @@ namespace api.Services { public interface IDatasetService { - Dataset GetOneDataset(string username, string name); + Dataset GetOneDataset(string userId, string name); Dataset GetOneDataset(string id); - List<Dataset> SearchDatasets(string name, string username); - List<Dataset> GetMyDatasets(string username); - List<Dataset> SortDatasets(string username, bool ascdsc, int latest); + List<Dataset> SearchDatasets(string name); + List<Dataset> GetMyDatasets(string userId); + List<Dataset> SortDatasets(string userId, bool ascdsc, int latest); List<Dataset> GetPublicDatasets(); Dataset Create(Dataset dataset); - void Update(string username, string name, Dataset dataset); - void Delete(string username, string name); + void Update(string userId, string id, Dataset dataset); + void Delete(string userId, string id); public List<Dataset> GetGuestDatasets(); public void Update(Dataset dataset); string GetDatasetId(string fileId); diff --git a/backend/api/api/Services/IMlConnectionService.cs b/backend/api/api/Services/IMlConnectionService.cs index d161bf49..d5dda9f2 100644 --- a/backend/api/api/Services/IMlConnectionService.cs +++ b/backend/api/api/Services/IMlConnectionService.cs @@ -8,6 +8,9 @@ namespace api.Services Task<string> SendModelAsync(object model, object dataset); Task PreProcess(Dataset dataset, string filePath,string id); Task TrainModel(Model model, Experiment experiment, string filePath, Dataset dataset, string id); + + Task<string> Predict(Predictor predictor, Experiment experiment, PredictorColumns[] inputs); + //Task<Dataset> PreProcess(Dataset dataset, byte[] file, string filename); } }
\ No newline at end of file diff --git a/backend/api/api/Services/IModelService.cs b/backend/api/api/Services/IModelService.cs index e10b8f3b..00299979 100644 --- a/backend/api/api/Services/IModelService.cs +++ b/backend/api/api/Services/IModelService.cs @@ -5,16 +5,17 @@ namespace api.Services { public interface IModelService { - Model GetOneModel(string username, string name); + Model GetOneModel(string userId, string name); Model GetOneModel(string id); - List<Model> GetMyModels(string username); - List<Model> GetLatestModels(string username); + List<Model> GetMyModels(string userId); + List<Model> GetMyModelsByType(string userId, string problemType); + List<Model> GetLatestModels(string userId); //List<Model> GetPublicModels(); Model Create(Model model); Model Replace(Model model); - void Update(string username, string name, Model model); + void Update(string userId, string name, Model model); public void Update(string id, Model model); - void Delete(string username, string name); + void Delete(string userId, string name); bool CheckHyperparameters(int inputNeurons, int hiddenLayerNeurons, int hiddenLayers, int outputNeurons); bool CheckDb(); } diff --git a/backend/api/api/Services/IPredictorService.cs b/backend/api/api/Services/IPredictorService.cs index 729dd0b6..16f0432a 100644 --- a/backend/api/api/Services/IPredictorService.cs +++ b/backend/api/api/Services/IPredictorService.cs @@ -1,19 +1,16 @@ -using System; -using api.Models; +using api.Models; namespace api.Services { - public interface IPredictorService - { - Predictor GetOnePredictor(string username, string name); - Predictor GetPredictor(string username, string GetPredictor); - List<Predictor> SearchPredictors(string name, string username); - List<Predictor> GetMyPredictors(string username); - List<Predictor> SortPredictors(string username, bool ascdsc, int latest); - List<Predictor> GetPublicPredictors(); + public interface IPredictorService + { Predictor Create(Predictor predictor); - void Update(string username, string name, Predictor predictor); - void Delete(string username, string name); + void Delete(string id, string userId); + List<Predictor> GetMyPredictors(string userId); + Predictor GetOnePredictor(string id); + Predictor GetPredictor(string userId, string id); + List<Predictor> GetPublicPredictors(); + List<Predictor> SortPredictors(string userId, bool ascdsc, int latest); + void Update(string id, Predictor predictor); } -} - +}
\ No newline at end of file diff --git a/backend/api/api/Services/MlApiCheckActionFilter.cs b/backend/api/api/Services/MlApiCheckActionFilter.cs new file mode 100644 index 00000000..d1c020b0 --- /dev/null +++ b/backend/api/api/Services/MlApiCheckActionFilter.cs @@ -0,0 +1,50 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace api.Services +{ + public class MlApiCheckActionFilter:ActionFilterAttribute + { + private readonly ILogger _logger; + private readonly string _safelist; + + public MlApiCheckActionFilter(string safelist,ILogger logger) + { + _logger = logger; + _safelist = safelist; + } + public override void OnActionExecuting(ActionExecutingContext context) + { + var remoteIp = context.HttpContext.Connection.RemoteIpAddress; + _logger.LogDebug("Remote IpAddress: {RemoteIp}", remoteIp); + var ip = _safelist.Split(';'); + var badIp = true; + + if (remoteIp.IsIPv4MappedToIPv6) + { + remoteIp = remoteIp.MapToIPv4(); + } + + foreach (var address in ip) + { + var testIp = IPAddress.Parse(address); + + if (testIp.Equals(remoteIp)) + { + badIp = false; + break; + } + } + + if (badIp) + { + _logger.LogWarning("Forbidden Request from IP: {RemoteIp}", remoteIp); + context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden); + return; + } + + base.OnActionExecuting(context); + } + } +} diff --git a/backend/api/api/Services/MlConnectionService.cs b/backend/api/api/Services/MlConnectionService.cs index bde9ce4c..17c0f8b8 100644 --- a/backend/api/api/Services/MlConnectionService.cs +++ b/backend/api/api/Services/MlConnectionService.cs @@ -14,8 +14,9 @@ namespace api.Services private readonly IModelService _modelService; private readonly IHubContext<ChatHub> _ichat; private readonly IConfiguration _configuration; + private readonly IFileService _fileService; - public MlConnectionService(IConfiguration configuration,IDatasetService datasetService,IHubContext<ChatHub> ichat) + public MlConnectionService(IConfiguration configuration,IDatasetService datasetService,IHubContext<ChatHub> ichat, IFileService fileService) { _configuration = configuration; @@ -23,6 +24,7 @@ namespace api.Services _datasetService=datasetService; _ichat=ichat; + _fileService = fileService; } public async Task<string> SendModelAsync(object model, object dataset)//Don't Use @@ -41,11 +43,7 @@ namespace api.Services //request.AddFile("file", file,filename); request.AddFile("file", filePath); request.AddHeader("Content-Type", "multipart/form-data"); - var result = await this.client.ExecuteAsync(request); - - if (ChatHub.CheckUser(id)) - await _ichat.Clients.Client(ChatHub.Users[id]).SendAsync("NotifyModel",model.name,model._id); - + this.client.ExecuteAsync(request); return; } @@ -67,8 +65,20 @@ namespace api.Services } - + public async Task<string> Predict(Predictor predictor, Experiment experiment, PredictorColumns[] inputs) + { + string filePath = _fileService.GetFilePath(predictor.h5FileId, predictor.uploaderId); + var request = new RestRequest("predict", Method.Post); + request.AddParameter("predictor", JsonConvert.SerializeObject(predictor)); + request.AddParameter("experiment", JsonConvert.SerializeObject(experiment)); + request.AddFile("file", filePath); + request.AddHeader("Content-Type", "multipart/form-data"); + + var result = await this.client.ExecuteAsync(request); + return result.Content; + + } } } diff --git a/backend/api/api/Services/ModelService.cs b/backend/api/api/Services/ModelService.cs index c21844f7..c35e5374 100644 --- a/backend/api/api/Services/ModelService.cs +++ b/backend/api/api/Services/ModelService.cs @@ -26,18 +26,22 @@ namespace api.Services return model; } - public void Delete(string username, string name) + public void Delete(string userId, string name) { - _model.DeleteOne(model => (model.username == username && model.name == name)); + _model.DeleteOne(model => (model.uploaderId == userId && model.name == name)); } - public List<Model> GetMyModels(string username) + public List<Model> GetMyModels(string userId) { - return _model.Find(model => model.username == username).ToList(); + return _model.Find(model => model.uploaderId == userId).ToList(); } - public List<Model> GetLatestModels(string username) + public List<Model> GetMyModelsByType(string userId, string problemType) { - List<Model> list = _model.Find(model => model.username == username).ToList(); + return _model.Find(model => (model.uploaderId == userId && model.type == problemType)).ToList(); + } + public List<Model> GetLatestModels(string userId) + { + List<Model> list = _model.Find(model => model.uploaderId == userId).ToList(); list = list.OrderByDescending(model => model.lastUpdated).ToList(); @@ -50,9 +54,9 @@ namespace api.Services return _model.Find(model => model.isPublic == true).ToList(); } */ - public Model GetOneModel(string username, string name) + public Model GetOneModel(string userId, string name) { - return _model.Find(model => model.username == username && model.name == name).FirstOrDefault(); + return _model.Find(model => model.uploaderId == userId && model.name == name).FirstOrDefault(); } public Model GetOneModel(string id) @@ -60,9 +64,9 @@ namespace api.Services return _model.Find(model => model._id == id).FirstOrDefault(); } - public void Update(string username, string name, Model model) + public void Update(string userId, string name, Model model) { - _model.ReplaceOne(model => model.username == username && model.name == name, model); + _model.ReplaceOne(model => model.uploaderId == userId && model.name == name, model); } public void Update(string id, Model model) { @@ -83,7 +87,7 @@ namespace api.Services public bool CheckDb() { Model? model = null; - model = _model.Find(model => model.username == "igrannonica").FirstOrDefault(); + model = _model.Find(model => model.uploaderId == "Igrannonica").FirstOrDefault(); if (model != null) return false; diff --git a/backend/api/api/Services/PredictorService.cs b/backend/api/api/Services/PredictorService.cs index b89eaded..756cc943 100644 --- a/backend/api/api/Services/PredictorService.cs +++ b/backend/api/api/Services/PredictorService.cs @@ -14,10 +14,6 @@ namespace api.Services _predictor = database.GetCollection<Predictor>(settings.PredictorCollectionName); } - public List<Predictor> SearchPredictors(string name, string username) - { - return _predictor.Find(predictor => predictor.name == name && predictor.isPublic == true).ToList(); - } public Predictor Create(Predictor predictor) { @@ -25,30 +21,30 @@ namespace api.Services return predictor; } - public void Delete(string username, string name) + public void Delete(string id, string userId) { - _predictor.DeleteOne(predictor => (predictor.username == username && predictor.name == name)); + _predictor.DeleteOne(predictor => (predictor._id == id && predictor.uploaderId == userId)); } - public List<Predictor> GetMyPredictors(string username) + public List<Predictor> GetMyPredictors(string userId) { - return _predictor.Find(predictor => predictor.username == username).ToList(); + return _predictor.Find(predictor => predictor.uploaderId == userId).ToList(); } - public Predictor GetOnePredictor(string username, string name) + public Predictor GetOnePredictor(string id) { - return _predictor.Find(predictor => predictor.username == username && predictor.name == name).FirstOrDefault(); + return _predictor.Find(predictor => predictor._id == id).FirstOrDefault(); } - public Predictor GetPredictor(string username, string id) + public Predictor GetPredictor(string userId, string id) { - return _predictor.Find(predictor => predictor._id == id && (predictor.username == username || predictor.isPublic == true)).FirstOrDefault(); + return _predictor.Find(predictor => predictor._id == id && (predictor.uploaderId == userId || predictor.isPublic == true)).FirstOrDefault(); } - public List<Predictor> SortPredictors(string username, bool ascdsc, int latest) + public List<Predictor> SortPredictors(string userId, bool ascdsc, int latest) { - List<Predictor> list = _predictor.Find(predictor => predictor.username == username).ToList(); + List<Predictor> list = _predictor.Find(predictor => predictor.uploaderId == userId).ToList(); if (ascdsc) list = list.OrderBy(predictor => predictor.dateCreated).ToList(); @@ -62,9 +58,9 @@ namespace api.Services return _predictor.Find(predictor => predictor.isPublic == true).ToList(); } - public void Update(string username, string name, Predictor predictor) + public void Update(string id, Predictor predictor) { - _predictor.ReplaceOne(predictor => predictor.username == username && predictor.name == name, predictor); + _predictor.ReplaceOne(predictor => predictor._id == id, predictor); } } } diff --git a/backend/api/api/Services/TempRemovalService.cs b/backend/api/api/Services/TempRemovalService.cs index e7f366a3..302ca974 100644 --- a/backend/api/api/Services/TempRemovalService.cs +++ b/backend/api/api/Services/TempRemovalService.cs @@ -27,7 +27,7 @@ namespace api.Services if ((DateTime.Now.ToUniversalTime() - file.date).TotalDays >= 1) { DeleteFile(file._id); - List<Dataset> datasets = _dataset.Find(dataset => dataset.fileId == file._id && dataset.username=="").ToList(); + List<Dataset> datasets = _dataset.Find(dataset => dataset.fileId == file._id && dataset.uploaderId=="").ToList(); foreach(var dataset in datasets) { DeleteDataset(dataset._id); @@ -37,7 +37,7 @@ namespace api.Services DeleteExperiment(experiment._id); foreach(var modelId in experiment.ModelIds) { - var delModel=_model.Find(model=> modelId== model._id && model.username=="").FirstOrDefault(); + var delModel=_model.Find(model=> modelId== model._id && model.uploaderId=="").FirstOrDefault(); if(delModel!= null) DeleteModel(delModel._id); } @@ -48,7 +48,7 @@ namespace api.Services } } //Brisanje modela ukoliko gost koristi vec postojeci dataset - List<Model> models1= _model.Find(model =>model.username == "").ToList(); + List<Model> models1= _model.Find(model =>model.uploaderId == "").ToList(); foreach(var model in models1) { if ((DateTime.Now.ToUniversalTime() - model.dateCreated.ToUniversalTime()).TotalDays >= 1) diff --git a/backend/api/api/Services/UserService.cs b/backend/api/api/Services/UserService.cs index 7fc4bdb1..b53f5fdc 100644 --- a/backend/api/api/Services/UserService.cs +++ b/backend/api/api/Services/UserService.cs @@ -51,17 +51,17 @@ namespace api.Services using (var session = _client.StartSession()) { if(username!=user.Username) - if(_users.Find(u => u.Username == user.Username).FirstOrDefault()!=null) - { - return false; - } + if(_users.Find(u => u.Username == user.Username).FirstOrDefault()!=null) + { + return false; + } //Trenutan MongoDB Server ne podrzava transakcije.Omoguciti Podrsku //session.StartTransaction(); try { _users.ReplaceOne(user => user.Username == username, user); - if (username != user.Username) + /*if (username != user.Username) { var builderModel = Builders<Model>.Update; var builderDataset = Builders<Dataset>.Update; @@ -70,7 +70,7 @@ namespace api.Services _datasets.UpdateMany(x => x.username == username, builderDataset.Set(x => x.username, user.Username)); _predictors.UpdateMany(x => x.username == username, builderPredictor.Set(x => x.username, user.Username)); } - + */ //session.AbortTransaction(); diff --git a/backend/api/api/UploadedFiles/Igrannonica/iris.csv b/backend/api/api/UploadedFiles/Igrannonica/iris.csv new file mode 100644 index 00000000..0713e5cb --- /dev/null +++ b/backend/api/api/UploadedFiles/Igrannonica/iris.csv @@ -0,0 +1,151 @@ +sepal_length,sepal_width,petal_length,petal_width,class +5.1,3.5,1.4,0.2,Iris-setosa +4.9,3.0,1.4,0.2,Iris-setosa +4.7,3.2,1.3,0.2,Iris-setosa +4.6,3.1,1.5,0.2,Iris-setosa +5.0,3.6,1.4,0.2,Iris-setosa +5.4,3.9,1.7,0.4,Iris-setosa +4.6,3.4,1.4,0.3,Iris-setosa +5.0,3.4,1.5,0.2,Iris-setosa +4.4,2.9,1.4,0.2,Iris-setosa +4.9,3.1,1.5,0.1,Iris-setosa +5.4,3.7,1.5,0.2,Iris-setosa +4.8,3.4,1.6,0.2,Iris-setosa +4.8,3.0,1.4,0.1,Iris-setosa +4.3,3.0,1.1,0.1,Iris-setosa +5.8,4.0,1.2,0.2,Iris-setosa +5.7,4.4,1.5,0.4,Iris-setosa +5.4,3.9,1.3,0.4,Iris-setosa +5.1,3.5,1.4,0.3,Iris-setosa +5.7,3.8,1.7,0.3,Iris-setosa +5.1,3.8,1.5,0.3,Iris-setosa +5.4,3.4,1.7,0.2,Iris-setosa +5.1,3.7,1.5,0.4,Iris-setosa +4.6,3.6,1.0,0.2,Iris-setosa +5.1,3.3,1.7,0.5,Iris-setosa +4.8,3.4,1.9,0.2,Iris-setosa +5.0,3.0,1.6,0.2,Iris-setosa +5.0,3.4,1.6,0.4,Iris-setosa +5.2,3.5,1.5,0.2,Iris-setosa +5.2,3.4,1.4,0.2,Iris-setosa +4.7,3.2,1.6,0.2,Iris-setosa +4.8,3.1,1.6,0.2,Iris-setosa +5.4,3.4,1.5,0.4,Iris-setosa +5.2,4.1,1.5,0.1,Iris-setosa +5.5,4.2,1.4,0.2,Iris-setosa +4.9,3.1,1.5,0.1,Iris-setosa +5.0,3.2,1.2,0.2,Iris-setosa +5.5,3.5,1.3,0.2,Iris-setosa +4.9,3.1,1.5,0.1,Iris-setosa +4.4,3.0,1.3,0.2,Iris-setosa +5.1,3.4,1.5,0.2,Iris-setosa +5.0,3.5,1.3,0.3,Iris-setosa +4.5,2.3,1.3,0.3,Iris-setosa +4.4,3.2,1.3,0.2,Iris-setosa +5.0,3.5,1.6,0.6,Iris-setosa +5.1,3.8,1.9,0.4,Iris-setosa +4.8,3.0,1.4,0.3,Iris-setosa +5.1,3.8,1.6,0.2,Iris-setosa +4.6,3.2,1.4,0.2,Iris-setosa +5.3,3.7,1.5,0.2,Iris-setosa +5.0,3.3,1.4,0.2,Iris-setosa +7.0,3.2,4.7,1.4,Iris-versicolor +6.4,3.2,4.5,1.5,Iris-versicolor +6.9,3.1,4.9,1.5,Iris-versicolor +5.5,2.3,4.0,1.3,Iris-versicolor +6.5,2.8,4.6,1.5,Iris-versicolor +5.7,2.8,4.5,1.3,Iris-versicolor +6.3,3.3,4.7,1.6,Iris-versicolor +4.9,2.4,3.3,1.0,Iris-versicolor +6.6,2.9,4.6,1.3,Iris-versicolor +5.2,2.7,3.9,1.4,Iris-versicolor +5.0,2.0,3.5,1.0,Iris-versicolor +5.9,3.0,4.2,1.5,Iris-versicolor +6.0,2.2,4.0,1.0,Iris-versicolor +6.1,2.9,4.7,1.4,Iris-versicolor +5.6,2.9,3.6,1.3,Iris-versicolor +6.7,3.1,4.4,1.4,Iris-versicolor +5.6,3.0,4.5,1.5,Iris-versicolor +5.8,2.7,4.1,1.0,Iris-versicolor +6.2,2.2,4.5,1.5,Iris-versicolor +5.6,2.5,3.9,1.1,Iris-versicolor +5.9,3.2,4.8,1.8,Iris-versicolor +6.1,2.8,4.0,1.3,Iris-versicolor +6.3,2.5,4.9,1.5,Iris-versicolor +6.1,2.8,4.7,1.2,Iris-versicolor +6.4,2.9,4.3,1.3,Iris-versicolor +6.6,3.0,4.4,1.4,Iris-versicolor +6.8,2.8,4.8,1.4,Iris-versicolor +6.7,3.0,5.0,1.7,Iris-versicolor +6.0,2.9,4.5,1.5,Iris-versicolor +5.7,2.6,3.5,1.0,Iris-versicolor +5.5,2.4,3.8,1.1,Iris-versicolor +5.5,2.4,3.7,1.0,Iris-versicolor +5.8,2.7,3.9,1.2,Iris-versicolor +6.0,2.7,5.1,1.6,Iris-versicolor +5.4,3.0,4.5,1.5,Iris-versicolor +6.0,3.4,4.5,1.6,Iris-versicolor +6.7,3.1,4.7,1.5,Iris-versicolor +6.3,2.3,4.4,1.3,Iris-versicolor +5.6,3.0,4.1,1.3,Iris-versicolor +5.5,2.5,4.0,1.3,Iris-versicolor +5.5,2.6,4.4,1.2,Iris-versicolor +6.1,3.0,4.6,1.4,Iris-versicolor +5.8,2.6,4.0,1.2,Iris-versicolor +5.0,2.3,3.3,1.0,Iris-versicolor +5.6,2.7,4.2,1.3,Iris-versicolor +5.7,3.0,4.2,1.2,Iris-versicolor +5.7,2.9,4.2,1.3,Iris-versicolor +6.2,2.9,4.3,1.3,Iris-versicolor +5.1,2.5,3.0,1.1,Iris-versicolor +5.7,2.8,4.1,1.3,Iris-versicolor +6.3,3.3,6.0,2.5,Iris-virginica +5.8,2.7,5.1,1.9,Iris-virginica +7.1,3.0,5.9,2.1,Iris-virginica +6.3,2.9,5.6,1.8,Iris-virginica +6.5,3.0,5.8,2.2,Iris-virginica +7.6,3.0,6.6,2.1,Iris-virginica +4.9,2.5,4.5,1.7,Iris-virginica +7.3,2.9,6.3,1.8,Iris-virginica +6.7,2.5,5.8,1.8,Iris-virginica +7.2,3.6,6.1,2.5,Iris-virginica +6.5,3.2,5.1,2.0,Iris-virginica +6.4,2.7,5.3,1.9,Iris-virginica +6.8,3.0,5.5,2.1,Iris-virginica +5.7,2.5,5.0,2.0,Iris-virginica +5.8,2.8,5.1,2.4,Iris-virginica +6.4,3.2,5.3,2.3,Iris-virginica +6.5,3.0,5.5,1.8,Iris-virginica +7.7,3.8,6.7,2.2,Iris-virginica +7.7,2.6,6.9,2.3,Iris-virginica +6.0,2.2,5.0,1.5,Iris-virginica +6.9,3.2,5.7,2.3,Iris-virginica +5.6,2.8,4.9,2.0,Iris-virginica +7.7,2.8,6.7,2.0,Iris-virginica +6.3,2.7,4.9,1.8,Iris-virginica +6.7,3.3,5.7,2.1,Iris-virginica +7.2,3.2,6.0,1.8,Iris-virginica +6.2,2.8,4.8,1.8,Iris-virginica +6.1,3.0,4.9,1.8,Iris-virginica +6.4,2.8,5.6,2.1,Iris-virginica +7.2,3.0,5.8,1.6,Iris-virginica +7.4,2.8,6.1,1.9,Iris-virginica +7.9,3.8,6.4,2.0,Iris-virginica +6.4,2.8,5.6,2.2,Iris-virginica +6.3,2.8,5.1,1.5,Iris-virginica +6.1,2.6,5.6,1.4,Iris-virginica +7.7,3.0,6.1,2.3,Iris-virginica +6.3,3.4,5.6,2.4,Iris-virginica +6.4,3.1,5.5,1.8,Iris-virginica +6.0,3.0,4.8,1.8,Iris-virginica +6.9,3.1,5.4,2.1,Iris-virginica +6.7,3.1,5.6,2.4,Iris-virginica +6.9,3.1,5.1,2.3,Iris-virginica +5.8,2.7,5.1,1.9,Iris-virginica +6.8,3.2,5.9,2.3,Iris-virginica +6.7,3.3,5.7,2.5,Iris-virginica +6.7,3.0,5.2,2.3,Iris-virginica +6.3,2.5,5.0,1.9,Iris-virginica +6.5,3.0,5.2,2.0,Iris-virginica +6.2,3.4,5.4,2.3,Iris-virginica +5.9,3.0,5.1,1.8,Iris-virginica diff --git a/backend/api/api/api.csproj b/backend/api/api/api.csproj index 7275f67c..3d245333 100644 --- a/backend/api/api/api.csproj +++ b/backend/api/api/api.csproj @@ -21,6 +21,7 @@ </ItemGroup> <ItemGroup> + <Folder Include="PredictorFiles\" /> <Folder Include="TempFiles\" /> </ItemGroup> diff --git a/backend/api/api/appsettings.json b/backend/api/api/appsettings.json index 42f62b75..c2310978 100644 --- a/backend/api/api/appsettings.json +++ b/backend/api/api/appsettings.json @@ -1,27 +1,29 @@ { "AppSettings": { "JwtToken": "2mnttqPtRb4GIWHFtagm", - "MlApi": "http://127.0.0.1:5543" + "MlApi": "http://127.0.0.1:5543", + "FrontApi": "http://localhost:4200", + "MlIp":"127.0.0.1;::1" }, - "Logging": { - "LogLevel": { - "Default": "Information", - "Microsoft.AspNetCore": "Warning" - } - }, - "AllowedHosts": "*", - "UserStoreDatabaseSettings": { - /* LocalHost*/ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "UserStoreDatabaseSettings": { + /* LocalHost*/ - "ConnectionString": "mongodb://127.0.0.1:27017/", - "DatabaseName": "si_project", - "CollectionName": "users", - "DatasetCollectionName": "Dataset", - "ModelCollectionName": "Model", - "PredictorCollectionName": "Predictor", - "FilesCollectionName": "Files", - "ExperimentCollectionName": "Experiment" - /* + "ConnectionString": "mongodb://127.0.0.1:27017/", + "DatabaseName": "si_project", + "CollectionName": "users", + "DatasetCollectionName": "Dataset", + "ModelCollectionName": "Model", + "PredictorCollectionName": "Predictor", + "FilesCollectionName": "Files", + "ExperimentCollectionName": "Experiment" + /* "ConnectionString": "mongodb+srv://si_user:si_user@sidatabase.twtfm.mongodb.net/myFirstDatabase?retryWrites=true&w=majority", "DatabaseName": "si_db", "CollectionName": "users", @@ -30,5 +32,5 @@ "PredictorCollectionName": "Predictor", "FilesCollectionName": "Files", "ExperimentCollectionName": "Experiment" */ - } + } }
\ No newline at end of file diff --git a/backend/microservice/api/config.py b/backend/microservice/api/config.py index 2b6fbe89..8fb088a7 100644 --- a/backend/microservice/api/config.py +++ b/backend/microservice/api/config.py @@ -1,2 +1,2 @@ api_url = "http://localhost:5283/api" - +hostIP = "127.0.0.1:5543"
\ No newline at end of file diff --git a/backend/microservice/api/controller.py b/backend/microservice/api/controller.py index 8e12c41d..f870b2b1 100644 --- a/backend/microservice/api/controller.py +++ b/backend/microservice/api/controller.py @@ -1,3 +1,7 @@ +from cmath import log +from dataclasses import dataclass +from distutils.command.upload import upload +from gc import callbacks import flask from flask import request, jsonify import newmlservice @@ -9,47 +13,101 @@ import config app = flask.Flask(__name__) app.config["DEBUG"] = True -app.config["SERVER_NAME"] = "127.0.0.1:5543" - +app.config["SERVER_NAME"] = config.hostIP + +#@dataclass +#class Predictor: +# _id : str + # username: str + # inputs : list + # output : str + # isPublic: bool + # accessibleByLink: bool + # dateCreated: DateTime + # experimentId: str + # modelId: str + # h5FileId: str + # metrics: list + + class train_callback(tf.keras.callbacks.Callback): - def __init__(self, x_test, y_test): + def __init__(self, x_test, y_test,modelId): self.x_test = x_test self.y_test = y_test + self.modelId=modelId # def on_epoch_end(self, epoch, logs=None): - print(epoch) + #print('Evaluation: ', self.model.evaluate(self.x_test,self.y_test),"\n") + + #print(epoch) + + #print(logs) + #ml_socket.send(epoch) #file = request.files.get("file") url = config.api_url + "/Model/epoch" - requests.post(url, epoch).text + r=requests.post(url, json={"Stat":str(logs),"ModelId":str(self.modelId),"EpochNum":epoch}).text + + #print(r) #print('Evaluation: ', self.model.evaluate(self.x_test,self.y_test),"\n") #broj parametara zavisi od izabranih metrika loss je default @app.route('/train', methods = ['POST']) def train(): - print("******************************TRAIN*************************************************") + #print("******************************TRAIN*************************************************") + f = request.files.get("file") data = pd.read_csv(f) paramsModel = json.loads(request.form["model"]) paramsExperiment = json.loads(request.form["experiment"]) paramsDataset = json.loads(request.form["dataset"]) #dataset, paramsModel, paramsExperiment, callback) - result = newmlservice.train(data, paramsModel, paramsExperiment,paramsDataset, train_callback) + filepath,result = newmlservice.train(data, paramsModel, paramsExperiment,paramsDataset, train_callback) + """ + f = request.json['filepath'] + dataset = pd.read_csv(f) + filepath,result=newmlservice.train(dataset,request.json['model'],train_callback) print(result) - return jsonify(result) + """ + + url = config.api_url + "/file/h5" + files = {'file': open(filepath, 'rb')} + r=requests.post(url, files=files,data={"uploaderId":paramsExperiment['uploaderId']}) + fileId=r.text + m = [] + for attribute, value in result.items(): + m.append({"Name" : attribute, "JsonValue" : value}) + predictor = { + "_id" : "", + "uploaderId" : paramsModel["uploaderId"], + "inputs" : paramsExperiment["inputColumns"], + "output" : paramsExperiment["outputColumn"], + "isPublic" : False, + "accessibleByLink" : False, + "experimentId" : paramsExperiment["_id"], + "modelId" : paramsModel["_id"], + "h5FileId" : fileId, + "metrics" : m + } + #print(predictor) + #print('\n') + url = config.api_url + "/Predictor/add" + r = requests.post(url, json=predictor).text + #print(r) + return r @app.route('/predict', methods = ['POST']) def predict(): - f = request.json['filepath'] - dataset = pd.read_csv(f) - m = request.json['modelpath'] - model = tf.keras.models.load_model(m) - print("********************************model loaded*******************************") - newmlservice.manageH5(dataset,request.json['model'],model) - return "done" + h5 = request.files.get("h5file") + model = tf.keras.models.load_model(h5) + paramsExperiment = json.loads(request.form["experiment"]) + paramsPredictor = json.loads(request.form["predictor"]) + #print("********************************model loaded*******************************") + result = newmlservice.predict(paramsExperiment, paramsPredictor, model) + return result @app.route('/preprocess',methods=['POST']) def returnColumnsInfo(): - print("********************************PREPROCESS*******************************") + #print("********************************PREPROCESS*******************************") dataset = json.loads(request.form["dataset"]) file = request.files.get("file") data=pd.read_csv(file) @@ -69,8 +127,8 @@ def returnColumnsInfo(): dataset["colCount"] = preprocess["colCount"] dataset["rowCount"] = preprocess["rowCount"] dataset["isPreProcess"] = True - print(dataset) + #print(dataset) return jsonify(dataset) -print("App loaded.") +#print("App loaded.") app.run()
\ No newline at end of file diff --git a/backend/microservice/api/newmlservice.py b/backend/microservice/api/newmlservice.py index d19a4e44..6e65c876 100644 --- a/backend/microservice/api/newmlservice.py +++ b/backend/microservice/api/newmlservice.py @@ -1,5 +1,6 @@ from enum import unique from itertools import count +import os import pandas as pd from sklearn import datasets, multiclass import tensorflow as tf @@ -21,6 +22,7 @@ from sklearn.model_selection import train_test_split from dataclasses import dataclass import statistics as s from sklearn.metrics import roc_auc_score +import matplotlib.pyplot as plt #from ann_visualizer.visualize import ann_viz; def returnColumnsInfo(dataset): dict=[] @@ -112,35 +114,48 @@ class TrainingResult: metrics: dict ''' -def train(dataset, params, callback): - problem_type = params["type"] +def train(dataset, paramsModel,paramsExperiment,paramsDataset,callback): + problem_type = paramsModel["type"] #print(problem_type) data = pd.DataFrame() #print(data) - for col in params["inputColumns"]: + for col in paramsExperiment["inputColumns"]: #print(col) data[col]=dataset[col] - output_column = params["columnToPredict"] + output_column = paramsExperiment["outputColumn"] data[output_column] = dataset[output_column] #print(data) ###NULL - null_value_options = params["nullValues"] - null_values_replacers = params["nullValuesReplacers"] - + null_value_options = paramsExperiment["nullValues"] + null_values_replacers = paramsExperiment["nullValuesReplacers"] + kategorijskekolone=data.select_dtypes(include=['object']).columns.copy() + #print(kategorijskekolone) if(null_value_options=='replace'): #print("replace null") # - dict=params['null_values_replacers'] + dict=null_values_replacers while(len(dict)>0): replace=dict.pop() col=replace['column'] opt=replace['option'] if(opt=='replace'): - replacevalue=replace['value'] - data[col]=data[col].fillna(replacevalue) + val = replace['value'] + if(data[col].dtype == 'int64'): + val = np.int64(val) + elif(data[col].dtype == 'float64'): + val = np.float64(val) + data[col]=data[col].fillna(val) elif(null_value_options=='delete_rows'): data=data.dropna() elif(null_value_options=='delete_columns'): + if(data[output_column].isnull().sum()>0): + if(output_column in kategorijskekolone): + replace=data[output_column].value_counts().index[0] + #print(replace) + else: + replace=data[output_column].mean() + data[output_column]=data[output_column].fillna(replace) + #print(data[output_column].isnull().sum()) data=data.dropna(axis=1) #print(data.shape) @@ -153,49 +168,74 @@ def train(dataset, params, callback): data.pop(col) # ### Enkodiranje - encoding=params["encoding"] - if(encoding=='label'): - encoder=LabelEncoder() - for col in data.columns: - if(data[col].dtype==np.object_): - data[col]=encoder.fit_transform(data[col]) + ''' + encodings=paramsExperiment["encodings"] + + from sklearn.preprocessing import LabelEncoder + kategorijskekolone=data.select_dtypes(include=['object']).columns + encoder=LabelEncoder() + for kolona in data.columns: + if(kolona in kategorijskekolone): + data[kolona]=encoder.fit_transform(data[kolona]) + ''' - elif(encoding=='onehot'): - category_columns=[] - for col in data.columns: - if(data[col].dtype==np.object_): - category_columns.append(col) - data=pd.get_dummies(data, columns=category_columns, prefix=category_columns) + encodings=paramsExperiment["encodings"] + datafront=dataset.copy() + svekolone=datafront.columns - elif(encoding=='ordinal'): - encoder = OrdinalEncoder() - for col in data.columns: - if(data[col].dtype==np.object_): - data[col]=encoder.fit_transform(data[col]) - - elif(encoding=='hashing'): - category_columns=[] - for col in data.columns: - if(data[col].dtype==np.object_): - category_columns.append(col) - encoder=ce.HashingEncoder(cols=category_columns, n_components=len(category_columns)) - encoder.fit_transform(data) - elif(encoding=='binary'): - category_columns=[] - for col in data.columns: - if(data[col].dtype==np.object_): - category_columns.append(col) - encoder=ce.BinaryEncoder(cols=category_columns, return_df=True) - encoder.fit_transform(data) - - elif(encoding=='baseN'): - category_columns=[] - for col in data.columns: - if(data[col].dtype==np.object_): - category_columns.append(col) - encoder=ce.BaseNEncoder(cols=category_columns, return_df=True, base=5) - encoder.fit_transform(data) + + for kolonaEncoding in encodings: + + kolona = kolonaEncoding["columnName"] + if kolona in data.columns: + encoding = kolonaEncoding["encoding"] + + if(kolona in kategorijskekolone): + if(encoding=='label'): + encoder=LabelEncoder() + for col in data.columns: + if(data[col].dtype==np.object_): + data[col]=encoder.fit_transform(data[col]) + + + elif(encoding=='onehot'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + data=pd.get_dummies(data, columns=category_columns, prefix=category_columns) + + elif(encoding=='ordinal'): + encoder = OrdinalEncoder() + for col in data.columns: + if(data[col].dtype==np.object_): + data[col]=encoder.fit_transform(data[col]) + + elif(encoding=='hashing'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + encoder=ce.HashingEncoder(cols=category_columns, n_components=len(category_columns)) + encoder.fit_transform(data) + elif(encoding=='binary'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + encoder=ce.BinaryEncoder(cols=category_columns, return_df=True) + encoder.fit_transform(data) + + elif(encoding=='baseN'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + encoder=ce.BaseNEncoder(cols=category_columns, return_df=True, base=5) + encoder.fit_transform(data) + + # # Input - output # @@ -210,8 +250,8 @@ def train(dataset, params, callback): # # Podela na test i trening skupove # - test=params["randomTestSetDistribution"] - randomOrder = params["randomOrder"] + test=paramsExperiment["randomTestSetDistribution"] + randomOrder = paramsExperiment["randomOrder"] if(randomOrder): random=123 else: @@ -224,7 +264,7 @@ def train(dataset, params, callback): # # ###OPTIMIZATORI - + """ if(params['optimizer']=='adam'): opt=tf.keras.optimizers.Adam(learning_rate=params['learningRate']) @@ -250,7 +290,7 @@ def train(dataset, params, callback): opt=tf.keras.optimizers.RMSprop(learning_rate=params['learningRate']) ###REGULARIZACIJA - #regularisation={'kernelType':'l1 ili l2 ili l1_l2','krenelRate':default=0.01 ili jedna od vrednosti(0.0001,0.001,0.1,1,2,3) ili neka koju je korisnik zadao,'biasType':'','biasRate':'','activityType','activityRate'} + #regularisation={'kernelType':'l1 ili l2 ili l1_l2','kernelRate':default=0.01 ili jedna od vrednosti(0.0001,0.001,0.1,1,2,3) ili neka koju je korisnik zadao,'biasType':'','biasRate':'','activityType','activityRate'} reg=params['regularisation'] ###Kernel @@ -276,49 +316,56 @@ def train(dataset, params, callback): activityreg=tf.keras.regularizers.l2(reg['activityRate']) elif(reg['kernelType']=='l1l2'): activityreg=tf.keras.regularizers.l1_l2(l1=reg['activityRate'][0],l2=reg['activityRate'][1]) - - + """ + filepath=os.path.join("temp/",paramsExperiment['_id']+"_"+paramsModel['_id']+".h5") if(problem_type=='multi-klasifikacioni'): #print('multi') classifier=tf.keras.Sequential() - classifier.add(tf.keras.layers.Dense(units=params['hiddenLayerNeurons'], activation=params['hiddenLayerActivationFunctions'][0],input_dim=x_train.shape[1]))#prvi skriveni + definisanje prethodnog-ulaznog - for i in range(params['hiddenLayers']-1):#ako postoji vise od jednog skrivenog sloja + classifier.add(tf.keras.layers.Dense(units=paramsModel['hiddenLayerNeurons'], activation=paramsModel['hiddenLayerActivationFunctions'][0],input_dim=x_train.shape[1]))#prvi skriveni + definisanje prethodnog-ulaznog + for i in range(paramsModel['hiddenLayers']-1):#ako postoji vise od jednog skrivenog sloja #print(i) - classifier.add(tf.keras.layers.Dense(units=params['hiddenLayerNeurons'], activation=params['hiddenLayerActivationFunctions'][i+1]))#i-ti skriveni sloj - classifier.add(tf.keras.layers.Dense(units=5, activation=params['outputLayerActivationFunction']))#izlazni sloj + classifier.add(tf.keras.layers.Dense(units=paramsModel['hiddenLayerNeurons'], activation=paramsModel['hiddenLayerActivationFunctions'][i+1]))#i-ti skriveni sloj + classifier.add(tf.keras.layers.Dense(units=5, activation=paramsModel['outputLayerActivationFunction']))#izlazni sloj - classifier.compile(loss =params["lossFunction"] , optimizer = params['optimizer'] , metrics =params['metrics']) - - history=classifier.fit(x_train, y_train, epochs = params['epochs'],batch_size=params['batchSize']) + classifier.compile(loss =paramsModel["lossFunction"] , optimizer = paramsModel['optimizer'] , metrics =['accuracy','mae','mse']) + history=classifier.fit(x_train, y_train, epochs = paramsModel['epochs'],batch_size=paramsModel['batchSize'],callbacks=callback(x_test, y_test,paramsModel['_id'])) + + hist=history.history + #plt.plot(hist['accuracy']) + #plt.show() y_pred=classifier.predict(x_test) y_pred=np.argmax(y_pred,axis=1) - #print(y_pred.flatten()) - #print(y_test) + scores = classifier.evaluate(x_test, y_test) #print("\n%s: %.2f%%" % (classifier.metrics_names[1], scores[1]*100)) - classifier.save("temp/"+params['name'], save_format='h5') + + + classifier.save(filepath, save_format='h5') + #vizuelizacija u python-u #from ann_visualizer.visualize import ann_viz; #ann_viz(classifier, title="My neural network") + + return filepath,hist elif(problem_type=='binarni-klasifikacioni'): #print('*************************************************************************binarni') classifier=tf.keras.Sequential() - classifier.add(tf.keras.layers.Dense(units=params['hiddenLayerNeurons'], activation=params['hiddenLayerActivationFunctions'][0],input_dim=x_train.shape[1]))#prvi skriveni + definisanje prethodnog-ulaznog - for i in range(params['hiddenLayers']-1):#ako postoji vise od jednog skrivenog sloja + classifier.add(tf.keras.layers.Dense(units=paramsModel['hiddenLayerNeurons'], activation=paramsModel['hiddenLayerActivationFunctions'][0],input_dim=x_train.shape[1]))#prvi skriveni + definisanje prethodnog-ulaznog + for i in range(paramsModel['hiddenLayers']-1):#ako postoji vise od jednog skrivenog sloja #print(i) - classifier.add(tf.keras.layers.Dense(units=params['hiddenLayerNeurons'], activation=params['hiddenLayerActivationFunctions'][i+1]))#i-ti skriveni sloj - classifier.add(tf.keras.layers.Dense(units=1, activation=params['outputLayerActivationFunction']))#izlazni sloj - - classifier.compile(loss =params["lossFunction"] , optimizer = params['optimizer'] , metrics =params['metrics']) + classifier.add(tf.keras.layers.Dense(units=paramsModel['hiddenLayerNeurons'], activation=paramsModel['hiddenLayerActivationFunctions'][i+1]))#i-ti skriveni sloj + classifier.add(tf.keras.layers.Dense(units=1, activation=paramsModel['outputLayerActivationFunction']))#izlazni sloj - history=classifier.fit(x_train, y_train, epochs = params['epochs'],batch_size=params['batchSize']) + classifier.compile(loss =paramsModel["lossFunction"] , optimizer = paramsModel['optimizer'] , metrics =['accuracy']) + history=classifier.fit(x_train, y_train, epochs = paramsModel['epochs'],batch_size=paramsModel['batchSize'],callbacks=callback(x_test, y_test,paramsModel['_id'])) + hist=history.history y_pred=classifier.predict(x_test) y_pred=(y_pred>=0.5).astype('int') @@ -329,23 +376,26 @@ def train(dataset, params, callback): #print("\n%s: %.2f%%" % (classifier.metrics_names[1], scores[1]*100)) #ann_viz(classifier, title="My neural network") - classifier.save("temp/"+params['name'], save_format='h5') + classifier.save(filepath, save_format='h5') + return filepath,hist elif(problem_type=='regresioni'): classifier=tf.keras.Sequential() - classifier.add(tf.keras.layers.Dense(units=params['hiddenLayerNeurons'], activation=params['hiddenLayerActivationFunctions'][0],input_dim=x_train.shape[1]))#prvi skriveni + definisanje prethodnog-ulaznog - for i in range(params['hiddenLayers']-1):#ako postoji vise od jednog skrivenog sloja + classifier.add(tf.keras.layers.Dense(units=paramsModel['hiddenLayerNeurons'], activation=paramsModel['hiddenLayerActivationFunctions'][0],input_dim=x_train.shape[1]))#prvi skriveni + definisanje prethodnog-ulaznog + for i in range(paramsModel['hiddenLayers']-1):#ako postoji vise od jednog skrivenog sloja #print(i) - classifier.add(tf.keras.layers.Dense(units=params['hiddenLayerNeurons'], activation=params['hiddenLayerActivationFunctions'][i+1]))#i-ti skriveni sloj + classifier.add(tf.keras.layers.Dense(units=paramsModel['hiddenLayerNeurons'], activation=paramsModel['hiddenLayerActivationFunctions'][i+1]))#i-ti skriveni sloj classifier.add(tf.keras.layers.Dense(units=1)) - classifier.compile(loss =params["lossFunction"] , optimizer = params['optimizer'] , metrics =params['metrics']) + classifier.compile(loss =paramsModel["lossFunction"] , optimizer = paramsModel['optimizer'] , metrics =['accuracy','mae','mse']) - history=classifier.fit(x_train, y_train, epochs = params['epochs'],batch_size=params['batchSize']) + history=classifier.fit(x_train, y_train, epochs = paramsModel['epochs'],batch_size=paramsModel['batchSize'],callbacks=callback(x_test, y_test,paramsModel['_id'])) + hist=history.history y_pred=classifier.predict(x_test) #print(classifier.evaluate(x_test, y_test)) - + classifier.save(filepath, save_format='h5') + return filepath,hist def roc_auc_score_multiclass(actual_class, pred_class, average = "macro"): #creating a set of all the unique classes using the actual class list @@ -427,6 +477,11 @@ def train(dataset, params, callback): micro_averaged_f1=metrics.f1_score(y_test, y_pred, average = 'micro') roc_auc_dict=roc_auc_score_multiclass(y_test, y_pred) ''' +def predict(experiment, predictor, model): + #model.predict() + # ovo je pre bilo manageH5 + return "TODO" + def manageH5(dataset,params,h5model): problem_type = params["type"] @@ -503,7 +558,7 @@ def manageH5(dataset,params,h5model): h5model.summary() #ann_viz(h5model, title="My neural network") - h5model.compile(loss=params['lossFunction'], optimizer=params['optimizer'], metrics=params['metrics']) + h5model.compile(loss=params['lossFunction'], optimizer=params['optimizer'], metrics=params['accuracy','']) history=h5model.fit(x2, y2, epochs = params['epochs'],batch_size=params['batchSize']) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 82cd01e6..d44f86d1 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -25,14 +25,16 @@ "@ng-bootstrap/ng-bootstrap": "^12.0.0", "@popperjs/core": "^2.10.2", "bootstrap": "^5.1.3", + "canvasjs": "^1.8.3", "chart.js": "^3.7.1", + "chartjs-plugin-datalabels": "^0.7.0", "csv-parser": "^3.0.0", "d3-graphviz": "^2.6.1", "jquery": "^3.6.0", "mdb-angular-ui-kit": "^2.0.0", "ng-multiselect-dropdown": "^0.3.8", "ng-uikit-pro-standard": "^1.0.0", - "ng2-charts": "^3.0.8", + "ng2-charts": "^3.0.9", "ng2-search-filter": "^0.5.1", "ngx-cookie-service": "^13.1.2", "popper.js": "^1.16.1", @@ -45,6 +47,7 @@ "@angular-devkit/build-angular": "~13.2.5", "@angular/cli": "~13.2.5", "@angular/compiler-cli": "~13.2.0", + "@types/canvasjs": "^1.9.7", "@types/crypto-js": "^4.1.1", "@types/d3-graphviz": "^2.6.7", "@types/jasmine": "~3.10.0", @@ -452,7 +455,6 @@ "version": "13.2.5", "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.2.5.tgz", "integrity": "sha512-Xd8xj2Z0ilA4TJAM/JkTtA1CAa6SuebFsEEvabHCRO5MDvtdsIUP91ADUZIqDHy7qe6Qift/rAVN2PXxT2aaNA==", - "dev": true, "dependencies": { "@babel/core": "^7.17.2", "chokidar": "^3.0.0", @@ -482,7 +484,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", - "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.0" }, @@ -494,7 +495,6 @@ "version": "7.17.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", - "dev": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", @@ -524,7 +524,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -533,7 +532,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", - "dev": true, "dependencies": { "@babel/types": "^7.17.0", "jsesc": "^2.5.1", @@ -547,7 +545,6 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -787,7 +784,6 @@ "version": "7.16.12", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -817,7 +813,6 @@ "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, "bin": { "semver": "bin/semver.js" } @@ -826,7 +821,6 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -835,7 +829,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", - "dev": true, "dependencies": { "@babel/types": "^7.16.8", "jsesc": "^2.5.1", @@ -849,7 +842,6 @@ "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -2748,6 +2740,12 @@ "@types/node": "*" } }, + "node_modules/@types/canvasjs": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@types/canvasjs/-/canvasjs-1.9.7.tgz", + "integrity": "sha512-/CfA3VtyTYVXDHvMy8F5hsvRUTiBX7TGe+nAHNONoJlNHbqakeIzxxt1MSNH619s8Oiu4CcFBMmJVg50El/0lw==", + "dev": true + }, "node_modules/@types/component-emitter": { "version": "1.2.11", "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", @@ -3376,7 +3374,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -3432,9 +3429,9 @@ } }, "node_modules/async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "dependencies": { "lodash": "^4.17.14" @@ -3650,7 +3647,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, "engines": { "node": ">=8" } @@ -3747,7 +3743,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "dependencies": { "fill-range": "^7.0.1" }, @@ -3897,6 +3892,11 @@ "url": "https://opencollective.com/browserslist" } }, + "node_modules/canvasjs": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/canvasjs/-/canvasjs-1.8.3.tgz", + "integrity": "sha512-60eUT0VjqRgYqdIQcOkXg0Zptfbl4HefA/O51YEf1m/P0uXvE3icI/1KPrXpY9aVxn8gG/BB8DzVoTGCcyBnYg==" + }, "node_modules/chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -3921,11 +3921,18 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.7.1.tgz", "integrity": "sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA==" }, + "node_modules/chartjs-plugin-datalabels": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-0.7.0.tgz", + "integrity": "sha512-PKVUX14nYhH0wcdCpgOoC39Gbzvn6cZ7O9n+bwc02yKD9FTnJ7/TSrBcfebmolFZp1Rcicr9xbT0a5HUbigS7g==", + "peerDependencies": { + "chart.js": ">= 2.7.0 < 3" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "funding": [ { "type": "individual", @@ -4944,7 +4951,6 @@ "version": "0.11.0", "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", - "dev": true, "engines": { "node": ">= 0.6.0" } @@ -5109,7 +5115,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, "optional": true, "dependencies": { "iconv-lite": "^0.6.2" @@ -5119,7 +5124,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "optional": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -5923,7 +5927,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -6092,7 +6095,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -6202,7 +6204,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -6795,7 +6796,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -6849,7 +6849,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6866,7 +6865,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -6893,7 +6891,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, "engines": { "node": ">=0.12.0" } @@ -7759,7 +7756,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "dependencies": { "yallist": "^4.0.0" }, @@ -7771,7 +7767,6 @@ "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", - "dev": true, "dependencies": { "sourcemap-codec": "^1.4.4" } @@ -8240,9 +8235,9 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/ng2-charts": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-3.0.8.tgz", - "integrity": "sha512-ELlpN0b/IJO4ka/P2sFBKeng3bV7XOQuh40f0J5hx9UveWPaSxOYQAOiGxV7BN2VSnKq6GRkjRvqTrcQPyJYww==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-3.0.9.tgz", + "integrity": "sha512-VKUd0lMhDb/IC4WGYH/CW29HlMibXGMK+Ik/T+M0uR98Ox+YEQUPsrqbroF/cWMxVf2XuTkFGaUn2fbqlDBSOQ==", "dependencies": { "lodash-es": "^4.17.15", "tslib": "^2.3.0" @@ -8396,7 +8391,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9092,7 +9086,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -9907,7 +9900,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "dependencies": { "picomatch": "^2.2.1" }, @@ -9918,8 +9910,7 @@ "node_modules/reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "dev": true + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" }, "node_modules/regenerate": { "version": "1.4.2", @@ -10209,7 +10200,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "devOptional": true }, "node_modules/sass": { "version": "1.49.0", @@ -10339,7 +10330,6 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -10724,8 +10714,7 @@ "node_modules/sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, "node_modules/spdy": { "version": "4.0.2", @@ -11126,7 +11115,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "dependencies": { "is-number": "^7.0.0" }, @@ -11218,7 +11206,6 @@ "version": "4.5.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", - "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -11858,8 +11845,7 @@ "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/yaml": { "version": "1.10.2", @@ -12194,7 +12180,6 @@ "version": "13.2.5", "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.2.5.tgz", "integrity": "sha512-Xd8xj2Z0ilA4TJAM/JkTtA1CAa6SuebFsEEvabHCRO5MDvtdsIUP91ADUZIqDHy7qe6Qift/rAVN2PXxT2aaNA==", - "dev": true, "requires": { "@babel/core": "^7.17.2", "chokidar": "^3.0.0", @@ -12212,7 +12197,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.1.2.tgz", "integrity": "sha512-hoyByceqwKirw7w3Z7gnIIZC3Wx3J484Y3L/cMpXFbr7d9ZQj2mODrirNzcJa+SM3UlpWXYvKV4RlRpFXlWgXg==", - "dev": true, "requires": { "@jridgewell/trace-mapping": "^0.3.0" } @@ -12221,7 +12205,6 @@ "version": "7.17.5", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.5.tgz", "integrity": "sha512-/BBMw4EvjmyquN5O+t5eh0+YqB3XXJkYD2cjKpYtWOfFy4lQ4UozNSmxAcWT8r2XtZs0ewG+zrfsqeR15i1ajA==", - "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", @@ -12243,8 +12226,7 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -12252,7 +12234,6 @@ "version": "7.17.3", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.3.tgz", "integrity": "sha512-+R6Dctil/MgUsZsZAkYgK+ADNSZzJRRy0TvY65T71z/CR854xHQ1EweBYXdfT+HNeN7w0cSJJEzgxZMv40pxsg==", - "dev": true, "requires": { "@babel/types": "^7.17.0", "jsesc": "^2.5.1", @@ -12262,8 +12243,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -12408,7 +12388,6 @@ "version": "7.16.12", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", - "dev": true, "requires": { "@babel/code-frame": "^7.16.7", "@babel/generator": "^7.16.8", @@ -12430,14 +12409,12 @@ "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -12445,7 +12422,6 @@ "version": "7.16.8", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", - "dev": true, "requires": { "@babel/types": "^7.16.8", "jsesc": "^2.5.1", @@ -12455,8 +12431,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -13617,7 +13592,8 @@ "ws": { "version": "7.5.7", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.7.tgz", - "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==" + "integrity": "sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==", + "requires": {} } } }, @@ -13633,7 +13609,8 @@ "version": "13.2.5", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.2.5.tgz", "integrity": "sha512-obiPvwPe+UJUO8cfNbBxukLKG30F+gLF5/erexwklRknJzS4KP8ciH2on6XlTuXUahpDjbO0pffugFE2I/IszQ==", - "dev": true + "dev": true, + "requires": {} }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -13792,6 +13769,12 @@ "@types/node": "*" } }, + "@types/canvasjs": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@types/canvasjs/-/canvasjs-1.9.7.tgz", + "integrity": "sha512-/CfA3VtyTYVXDHvMy8F5hsvRUTiBX7TGe+nAHNONoJlNHbqakeIzxxt1MSNH619s8Oiu4CcFBMmJVg50El/0lw==", + "dev": true + }, "@types/component-emitter": { "version": "1.2.11", "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", @@ -14233,7 +14216,8 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true + "dev": true, + "requires": {} }, "adjust-sourcemap-loader": { "version": "4.0.0", @@ -14356,7 +14340,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, "requires": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -14400,9 +14383,9 @@ "dev": true }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "requires": { "lodash": "^4.17.14" @@ -14554,8 +14537,7 @@ "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==" }, "bl": { "version": "4.1.0", @@ -14626,7 +14608,8 @@ "bootstrap": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", - "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==" + "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", + "requires": {} }, "brace-expansion": { "version": "1.1.11", @@ -14641,7 +14624,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, "requires": { "fill-range": "^7.0.1" } @@ -14745,6 +14727,11 @@ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001312.tgz", "integrity": "sha512-Wiz1Psk2MEK0pX3rUzWaunLTZzqS2JYZFzNKqAiJGiuxIjRPLgV6+VDPOg6lQOUxmDwhTlh198JsTTi8Hzw6aQ==" }, + "canvasjs": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/canvasjs/-/canvasjs-1.8.3.tgz", + "integrity": "sha512-60eUT0VjqRgYqdIQcOkXg0Zptfbl4HefA/O51YEf1m/P0uXvE3icI/1KPrXpY9aVxn8gG/BB8DzVoTGCcyBnYg==" + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -14766,11 +14753,16 @@ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.7.1.tgz", "integrity": "sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA==" }, + "chartjs-plugin-datalabels": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chartjs-plugin-datalabels/-/chartjs-plugin-datalabels-0.7.0.tgz", + "integrity": "sha512-PKVUX14nYhH0wcdCpgOoC39Gbzvn6cZ7O9n+bwc02yKD9FTnJ7/TSrBcfebmolFZp1Rcicr9xbT0a5HUbigS7g==", + "requires": {} + }, "chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, "requires": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -14798,7 +14790,8 @@ "version": "5.2.2", "resolved": "https://registry.npmjs.org/circular-dependency-plugin/-/circular-dependency-plugin-5.2.2.tgz", "integrity": "sha512-g38K9Cm5WRwlaH6g03B9OEz/0qRizI+2I7n+Gz+L5DxXJAPAiWQvwlYNm1V1jkdpUv95bOe/ASm2vfi/G560jQ==", - "dev": true + "dev": true, + "requires": {} }, "clean-stack": { "version": "2.2.0", @@ -15276,7 +15269,8 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-6.0.3.tgz", "integrity": "sha512-4BqMbZksRkJQx2zAjrokiGMd07RqOa2IxIrrN10lyBe9xhn9DEvjUK79J6jkeiv9D9hQFXKb6g1jwU62jziJZA==", - "dev": true + "dev": true, + "requires": {} }, "css-select": { "version": "4.2.1", @@ -15542,8 +15536,7 @@ "dependency-graph": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", - "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", - "dev": true + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==" }, "destroy": { "version": "1.0.4", @@ -15678,7 +15671,6 @@ "version": "0.1.13", "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, "optional": true, "requires": { "iconv-lite": "^0.6.2" @@ -15688,7 +15680,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "optional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -16212,7 +16203,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, "requires": { "to-regex-range": "^5.0.1" } @@ -16335,7 +16325,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, "optional": true }, "function-bind": { @@ -16411,7 +16400,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, "requires": { "is-glob": "^4.0.1" } @@ -16668,7 +16656,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true + "dev": true, + "requires": {} }, "ieee754": { "version": "1.2.1", @@ -16865,7 +16854,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, "requires": { "binary-extensions": "^2.0.0" } @@ -16897,8 +16885,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -16909,7 +16896,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -16929,8 +16915,7 @@ "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" }, "is-path-cwd": { "version": "2.2.0", @@ -17354,7 +17339,8 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-1.7.0.tgz", "integrity": "sha512-pzum1TL7j90DTE86eFt48/s12hqwQuiD+e5aXx2Dc9wDEn2LfGq6RoAxEZZjFiN0RDSCOnosEKRZWxbQ+iMpQQ==", - "dev": true + "dev": true, + "requires": {} }, "karma-source-map-support": { "version": "1.4.0", @@ -17576,7 +17562,6 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -17585,7 +17570,6 @@ "version": "0.25.7", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.7.tgz", "integrity": "sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==", - "dev": true, "requires": { "sourcemap-codec": "^1.4.4" } @@ -17940,9 +17924,9 @@ } }, "ng2-charts": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-3.0.8.tgz", - "integrity": "sha512-ELlpN0b/IJO4ka/P2sFBKeng3bV7XOQuh40f0J5hx9UveWPaSxOYQAOiGxV7BN2VSnKq6GRkjRvqTrcQPyJYww==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-3.0.9.tgz", + "integrity": "sha512-VKUd0lMhDb/IC4WGYH/CW29HlMibXGMK+Ik/T+M0uR98Ox+YEQUPsrqbroF/cWMxVf2XuTkFGaUn2fbqlDBSOQ==", "requires": { "lodash-es": "^4.17.15", "tslib": "^2.3.0" @@ -18046,8 +18030,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-range": { "version": "0.1.2", @@ -18580,8 +18563,7 @@ "picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" }, "pify": { "version": "2.3.0", @@ -18697,7 +18679,8 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-8.0.0.tgz", "integrity": "sha512-FvO2GzMUaTN0t1fBULDeIvxr5IvbDXcIatt6pnJghc736nqNgsGao5NT+5+WVLAQiTt6Cb3YUms0jiPaXhL//g==", - "dev": true + "dev": true, + "requires": {} }, "postcss-custom-properties": { "version": "12.1.4", @@ -18767,13 +18750,15 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "dev": true + "dev": true, + "requires": {} }, "postcss-gap-properties": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-3.0.3.tgz", "integrity": "sha512-rPPZRLPmEKgLk/KlXMqRaNkYTUpE7YC+bOIQFN5xcu1Vp11Y4faIXv6/Jpft6FMnl6YRxZqDZG0qQOW80stzxQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-image-set-function": { "version": "4.0.6", @@ -18799,7 +18784,8 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-4.0.1.tgz", "integrity": "sha512-0ueD7rPqX8Pn1xJIjay0AZeIuDoF+V+VvMt/uOnn+4ezUKhZM/NokDeP6DwMNyIoYByuN/94IQnt5FEkaN59xQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-lab-function": { "version": "4.1.1", @@ -18826,19 +18812,22 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-5.0.4.tgz", "integrity": "sha512-RHXxplCeLh9VjinvMrZONq7im4wjWGlRJAqmAVLXyZaXwfDWP73/oq4NdIp+OZwhQUMj0zjqDfM5Fj7qby+B4g==", - "dev": true + "dev": true, + "requires": {} }, "postcss-media-minmax": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-5.0.0.tgz", "integrity": "sha512-yDUvFf9QdFZTuCUg0g0uNSHVlJ5X1lSzDZjPSFaiCWvjgsvu8vEVxtahPrLMinIDEEGnx6cBe6iqdx5YWz08wQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -18882,13 +18871,15 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-3.0.3.tgz", "integrity": "sha512-CxZwoWup9KXzQeeIxtgOciQ00tDtnylYIlJBBODqkgS/PU2jISuWOL/mYLHmZb9ZhZiCaNKsCRiLp22dZUtNsg==", - "dev": true + "dev": true, + "requires": {} }, "postcss-page-break": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "dev": true + "dev": true, + "requires": {} }, "postcss-place": { "version": "7.0.4", @@ -18953,7 +18944,8 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-selector-not": { "version": "5.0.0", @@ -19127,7 +19119,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, "requires": { "picomatch": "^2.2.1" } @@ -19135,8 +19126,7 @@ "reflect-metadata": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz", - "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==", - "dev": true + "integrity": "sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==" }, "regenerate": { "version": "1.4.2", @@ -19355,7 +19345,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "devOptional": true }, "sass": { "version": "1.49.0", @@ -19411,7 +19401,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "json-schema-traverse": { "version": "0.4.1", @@ -19440,7 +19431,6 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, "requires": { "lru-cache": "^6.0.0" } @@ -19756,8 +19746,7 @@ "sourcemap-codec": { "version": "1.4.8", "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, "spdy": { "version": "4.0.2", @@ -19965,7 +19954,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "json-schema-traverse": { "version": "0.4.1", @@ -20039,7 +20029,6 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, "requires": { "is-number": "^7.0.0" } @@ -20108,8 +20097,7 @@ "typescript": { "version": "4.5.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", - "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", - "dev": true + "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==" }, "ua-parser-js": { "version": "0.7.31", @@ -20318,7 +20306,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "json-schema-traverse": { "version": "0.4.1", @@ -20554,7 +20543,8 @@ "version": "8.2.3", "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true + "dev": true, + "requires": {} }, "y18n": { "version": "5.0.8", @@ -20564,8 +20554,7 @@ "yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yaml": { "version": "1.10.2", diff --git a/frontend/package.json b/frontend/package.json index c02a1fb0..87b21e05 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -28,14 +28,16 @@ "@ng-bootstrap/ng-bootstrap": "^12.0.0", "@popperjs/core": "^2.10.2", "bootstrap": "^5.1.3", + "canvasjs": "^1.8.3", "chart.js": "^3.7.1", + "chartjs-plugin-datalabels": "^0.7.0", "csv-parser": "^3.0.0", "d3-graphviz": "^2.6.1", "jquery": "^3.6.0", "mdb-angular-ui-kit": "^2.0.0", "ng-multiselect-dropdown": "^0.3.8", "ng-uikit-pro-standard": "^1.0.0", - "ng2-charts": "^3.0.8", + "ng2-charts": "^3.0.9", "ng2-search-filter": "^0.5.1", "ngx-cookie-service": "^13.1.2", "popper.js": "^1.16.1", @@ -48,6 +50,7 @@ "@angular-devkit/build-angular": "~13.2.5", "@angular/cli": "~13.2.5", "@angular/compiler-cli": "~13.2.0", + "@types/canvasjs": "^1.9.7", "@types/crypto-js": "^4.1.1", "@types/d3-graphviz": "^2.6.7", "@types/jasmine": "~3.10.0", diff --git a/frontend/src/app/_data/Dataset.ts b/frontend/src/app/_data/Dataset.ts index 732d1c56..766040a3 100644 --- a/frontend/src/app/_data/Dataset.ts +++ b/frontend/src/app/_data/Dataset.ts @@ -10,7 +10,7 @@ export default class Dataset { public accessibleByLink: boolean = false, public dateCreated: Date = new Date(), public lastUpdated: Date = new Date(), - public username: string = '', + public uploaderId: string = '', public delimiter: string = '', public hasHeader: boolean = true, diff --git a/frontend/src/app/_data/Experiment.ts b/frontend/src/app/_data/Experiment.ts index 453f6ca0..95ef6e1e 100644 --- a/frontend/src/app/_data/Experiment.ts +++ b/frontend/src/app/_data/Experiment.ts @@ -1,3 +1,4 @@ +import { ProblemType } from "./Model"; export default class Experiment { _id: string = ''; uploaderId: string = ''; @@ -18,8 +19,8 @@ export default class Experiment { public randomTestSet: boolean = true, public randomTestSetDistribution: number = 0.1, //0.1-0.9 (10% - 90%) JESTE OVDE ZAKUCANO 10, AL POSLATO JE KAO 0.1 BACK-U - //TODO - za svaku kolonu se bira enkoding - public encoding: Encoding = Encoding.Label + public encodings: ColumnEncoding[] = [], + public type: ProblemType = ProblemType.Regression ) { } } @@ -65,4 +66,12 @@ export enum Encoding { WOE = 'woe', Quantile = 'quantile' */ +} + +export class ColumnEncoding { + constructor ( + public columnName: string, + public encoding: Encoding + ) + {} }
\ No newline at end of file diff --git a/frontend/src/app/_data/Model.ts b/frontend/src/app/_data/Model.ts index 8a85e296..7d383584 100644 --- a/frontend/src/app/_data/Model.ts +++ b/frontend/src/app/_data/Model.ts @@ -16,10 +16,10 @@ export default class Model { public inputNeurons: number = 1, public hiddenLayerNeurons: number = 1, public hiddenLayers: number = 1, - public batchSize: number = 5, + public batchSize: number = 4, public hiddenLayerActivationFunctions: string[] = ['sigmoid'], public outputLayerActivationFunction: ActivationFunction = ActivationFunction.Sigmoid, - public username: string = '', + public uploaderId: string = '', public metrics: string[] = [], // TODO add to add-model form public epochs: number = 5 // TODO add to add-model form ) { } @@ -73,7 +73,7 @@ export enum LossFunction { HingeLoss = 'hinge_loss', // multi-class classification loss functions CategoricalCrossEntropy = 'categorical_crossentropy', - SparseCategoricalCrossEntropy = 'sparse_categorical_crosentropy', + SparseCategoricalCrossEntropy = 'sparse_categorical_crossentropy', KLDivergence = 'kullback_leibler_divergence', // regression loss functions @@ -94,8 +94,8 @@ export enum LossFunctionBinaryClassification { HingeLoss = 'hinge_loss', } export enum LossFunctionMultiClassification { - CategoricalCrossEntropy = 'categorical_crossentropy', - SparseCategoricalCrossEntropy = 'sparse_categorical_crosentropy', + //CategoricalCrossEntropy = 'categorical_crossentropy', + SparseCategoricalCrossEntropy = 'sparse_categorical_crossentropy', KLDivergence = 'kullback_leibler_divergence', } diff --git a/frontend/src/app/_data/Notification.ts b/frontend/src/app/_data/Notification.ts index c505d399..94a3be1d 100644 --- a/frontend/src/app/_data/Notification.ts +++ b/frontend/src/app/_data/Notification.ts @@ -1,5 +1,4 @@ export default class Notification { - _id: string = ''; constructor( public title: string = 'Treniranje u toku...', public id: string = '042', diff --git a/frontend/src/app/_data/Predictor.ts b/frontend/src/app/_data/Predictor.ts index 7e902eae..8aa2b6cb 100644 --- a/frontend/src/app/_data/Predictor.ts +++ b/frontend/src/app/_data/Predictor.ts @@ -7,6 +7,7 @@ export default class Predictor { public output: string = '', public isPublic: boolean = false, public accessibleByLink: boolean = false, - public dateCreated: Date = new Date() + public dateCreated: Date = new Date(), + public uploaderId: string = '' ) { } }
\ No newline at end of file diff --git a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.html b/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.html index bff8b022..e5d4cd23 100644 --- a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.html +++ b/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.html @@ -2,48 +2,54 @@ <div class="col-2"> </div> <div class="col-3"> - <label for="name" class="col-form-label">Naziv dataseta:</label> - <input type="text" class="form-control mb-1" name="name" placeholder="Naziv..." [(ngModel)]="dataset.name"> + <label for="name" class="col-form-label">Naziv dataseta:</label> + <input type="text" class="form-control mb-1" name="name" placeholder="Naziv..." [(ngModel)]="dataset.name"> - <label for="desc" class="col-sm-2 col-form-label">Opis:</label> - <div> - <textarea class="form-control" name="desc" rows="3" [(ngModel)]="dataset.description"></textarea> - </div> + <label for="desc" class="col-sm-2 col-form-label">Opis:</label> + <div> + <textarea class="form-control" name="desc" rows="3" [(ngModel)]="dataset.description"></textarea> + </div> - <label for="checkboxIsPublic" class="form-check-label mt-3 mb-1">Želite li da dataset bude javan? + <label for="checkboxIsPublic" class="form-check-label mt-3 mb-1">Želite li da dataset bude javan? <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="dataset.isPublic" (change)="checkAccessible()" type="checkbox" value="" id="checkboxIsPublic"> - </label> - - <label for="checkboxAccessibleByLink" class="form-check-label">Želite li da bude deljiv linkom? + </label> + + <label for="checkboxAccessibleByLink" class="form-check-label">Želite li da bude deljiv linkom? <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="dataset.accessibleByLink" type="checkbox" value="" id="checkboxAccessibleByLink"> </label> </div> <div class="col-1"> </div> - <div class="col-4 mt-4"> + <div class="col-3 mt-4"> - <input list="delimiterOptions" placeholder="Izaberite ili ukucajte delimiter za .csv fajl" class="form-control mt-2" - [(ngModel)]="dataset.delimiter" (input)="update()"> - <datalist id="delimiterOptions"> + <label for="type" class="col-form-label">Izaberite delimiter za .csv fajl</label> + <select id="delimiterOptions" class="form-select" name="type" [(ngModel)]="dataset.delimiter" (change)="update()"> + <option + *ngFor="let option of delimiterOptions"> + {{ option }} + </option> + </select> + <!-- + <input list="delimiterOptions" placeholder="Izaberite ili ukucajte delimiter za .csv fajl" class="form-control mt-2" [(ngModel)]="dataset.delimiter" (input)="update()"> + <datalist id="delimiterOptions"> <option *ngFor="let option of delimiterOptions">{{option}}</option> </datalist> - - <label for="type" class="form-check-label my-5">Da li .csv ima header? +--> + <label for="type" class="form-check-label my-5">Da li .csv ima header? <input class="mx-3 form-check-input" type="checkbox" (input)="update()" [(ngModel)]="dataset.hasHeader" type="checkbox" value="" id="checkboxHeader" checked> </label> - <br> - <input id="fileInput" class="form-control" type="file" class="upload" (change)="changeListener($event)" - accept=".csv"> + <br> + <input id="fileInput" class="form-control btn-lg" type="file" class="upload" (change)="changeListener($event)" accept=".csv"> </div> </div> <div class="px-5 mt-5"> - <app-datatable [tableData]="tableData"></app-datatable> + <app-datatable [tableData]="tableData"></app-datatable> </div> <div class="d-flex flex-row align-items-center justify-content-center w-100 my-2"> - <button (click)="uploadDataset()" class="btn btn-lg col-4" style="background-color:#003459; color:white;">Dodaj izvor podataka</button> -</div> + <button (click)="uploadDataset()" class="btn btn-lg col-4" style="background-color:#003459; color:white;">Dodaj izvor podataka</button> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.ts b/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.ts index 6ff108ce..1f395105 100644 --- a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.ts +++ b/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.ts @@ -13,10 +13,9 @@ import { CsvParseService } from 'src/app/_services/csv-parse.service'; }) export class AddNewDatasetComponent { - @Output() newDatasetAdded = new EventEmitter<string>(); @ViewChild(DatatableComponent) datatable!: DatatableComponent; - delimiterOptions: Array<string> = [",", ";", "\t", "razmak", "|"]; //podrazumevano "," + delimiterOptions: Array<string> = [",", ";", "|", "razmak", "novi red"]; //podrazumevano "," csvRecords: any[] = []; files: File[] = []; @@ -29,6 +28,7 @@ export class AddNewDatasetComponent { constructor(private modelsService: ModelsService, private datasetsService: DatasetsService, private csv: CsvParseService) { this.dataset = new Dataset(); + this.dataset.delimiter = ','; } //@ViewChild('fileImportInput', { static: false }) fileImportInput: any; cemu je ovo sluzilo? @@ -36,8 +36,6 @@ export class AddNewDatasetComponent { changeListener($event: any): void { this.files = $event.srcElement.files; if (this.files.length == 0 || this.files[0] == null) { - //console.log("NEMA FAJLA"); - //this.loaded.emit("not loaded"); this.tableData.hasInput = false; return; } @@ -56,7 +54,7 @@ export class AddNewDatasetComponent { const fileReader = new FileReader(); fileReader.onload = (e) => { if (typeof fileReader.result === 'string') { - const result = this.csv.csvToArray(fileReader.result, (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter == "") ? "," : this.dataset.delimiter) + const result = this.csv.csvToArray(fileReader.result, (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter == "novi red") ? "\t" : this.dataset.delimiter) if (this.dataset.hasHeader) this.csvRecords = result.splice(0, 11); @@ -90,10 +88,9 @@ export class AddNewDatasetComponent { this.modelsService.uploadData(this.files[0]).subscribe((file) => { //console.log('ADD MODEL: STEP 2 - ADD DATASET WITH FILE ID ' + file._id); this.dataset.fileId = file._id; - this.dataset.username = shared.username; + this.dataset.uploaderId = shared.userId; this.datasetsService.addDataset(this.dataset).subscribe((dataset) => { - this.newDatasetAdded.emit("added"); shared.openDialog("Obaveštenje", "Uspešno ste dodali novi izvor podataka u kolekciju. Molimo sačekajte par trenutaka da se procesira."); }, (error) => { shared.openDialog("Neuspeo pokušaj!", "Izvor podataka sa unetim nazivom već postoji u Vašoj kolekciji. Izmenite naziv ili iskoristite postojeći dataset."); diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.html b/frontend/src/app/_elements/dataset-load/dataset-load.component.html index 56a3b3c9..f244a882 100644 --- a/frontend/src/app/_elements/dataset-load/dataset-load.component.html +++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.html @@ -1,40 +1,34 @@ <div> - <!--Sklonjeno ucitavanje novog dataseta i sve opcije u vezi sa tim, premesteno u add-new-dataset--> + <!--Sklonjeno ucitavanje novog dataseta i sve opcije u vezi sa tim, premesteno u add-new-dataset--> <div class="d-flex flex-row justify-content-center align-items-center mt-3 mb-5"> - <button type="button" id="btnMyDataset" class="btn" (click)="viewMyDatasetsForm()" - [ngClass]="{'btnType1': showMyDatasets, 'btnType2': !showMyDatasets}"> + <button type="button" id="btnMyDataset" class="btn" (click)="viewMyDatasetsForm()" [ngClass]="{'btnType1': showMyDatasets, 'btnType2': !showMyDatasets}"> Izaberite dataset iz kolekcije </button> <h3 class="mt-3 mx-3">ili</h3> - <button type="button" id="btnNewDataset" class="btn" (click)="viewNewDatasetForm()" - [ngClass]="{'btnType1': !showMyDatasets, 'btnType2': showMyDatasets}"> + <button type="button" id="btnNewDataset" class="btn" (click)="viewNewDatasetForm()" [ngClass]="{'btnType1': !showMyDatasets, 'btnType2': showMyDatasets}"> Dodajte novi dataset </button> </div> - <div class="px-5 my-2"> - <input *ngIf="showMyDatasets" type="text" class="form-control" placeholder="Pretraga" - [(ngModel)]="term"> - </div> - <div class="px-5" *ngIf="showMyDatasets"> + <div class="px-5 my-2"> + <input *ngIf="showMyDatasets" type="text" class="form-control" placeholder="Pretraga" [(ngModel)]="term"> + </div> + <div class="px-5" *ngIf="showMyDatasets"> <div class="overflow-auto" style="max-height: 500px;"> <ul class="list-group"> - <li class="list-group-item p-3" *ngFor="let dataset of myDatasets|filter:term" - [ngClass]="{'selectedDatasetClass': this.selectedDataset == dataset}"> - <app-item-dataset name="usersDataset" [dataset]="dataset" - (click)="selectThisDataset(dataset);"></app-item-dataset> + <li class="list-group-item p-3" *ngFor="let dataset of myDatasets|filter:term" [ngClass]="{'selectedDatasetClass': this.selectedDataset == dataset}"> + <app-item-dataset name="usersDataset" [dataset]="dataset" (click)="selectThisDataset(dataset);"></app-item-dataset> </li> </ul> </div> <div class="px-5 mt-5"> <app-datatable [tableData]="tableData"></app-datatable> </div> - </div> + </div> - <app-add-new-dataset [style]="(showMyDatasets)?'display:none;visibility:hidden;':''" id="dataset" - (newDatasetAdded)="refreshMyDatasets()"> - </app-add-new-dataset> + <app-add-new-dataset [style]="(showMyDatasets)?'display:none;visibility:hidden;':''" id="dataset"> + </app-add-new-dataset> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.ts b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts index 62cca456..be1dc097 100644 --- a/frontend/src/app/_elements/dataset-load/dataset-load.component.ts +++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts @@ -8,6 +8,7 @@ import { DatasetsService } from 'src/app/_services/datasets.service'; import { CsvParseService } from 'src/app/_services/csv-parse.service'; import { Output, EventEmitter } from '@angular/core'; import { SignalRService } from 'src/app/_services/signal-r.service'; +import { AuthService } from 'src/app/_services/auth.service'; @Component({ selector: 'app-dataset-load', @@ -33,7 +34,15 @@ export class DatasetLoadComponent implements OnInit { term: string = ""; - constructor(private models: ModelsService, private datasets: DatasetsService, private csv: CsvParseService, private signalRService: SignalRService) { + constructor(private models: ModelsService, private datasets: DatasetsService, private csv: CsvParseService, private signalRService: SignalRService, private authService: AuthService) { + this.fetchDatasets(); + + authService.loggedInEvent.subscribe(_ => { + this.fetchDatasets(); + }) + } + + fetchDatasets() { this.datasets.getMyDatasets().subscribe((datasets) => { this.myDatasets = datasets; }); @@ -41,22 +50,17 @@ export class DatasetLoadComponent implements OnInit { viewMyDatasetsForm() { this.showMyDatasets = true; - this.resetSelectedDataset(); + if (this.selectedDataset != undefined) + this.resetSelectedDataset(); //this.resetCbsAndRbs(); //TREBA DA SE DESI } viewNewDatasetForm() { this.showMyDatasets = false; - this.resetSelectedDataset(); + if (this.selectedDataset != undefined) + this.resetSelectedDataset(); //this.resetCbsAndRbs(); //TREBA DA SE DESI } - refreshMyDatasets() { - this.datasets.getMyDatasets().subscribe((datasets) => { - this.myDatasets = datasets; - this.showMyDatasets = true; - }); - } - selectThisDataset(dataset: Dataset) { this.selectedDataset = dataset; this.selectedDatasetLoaded = false; @@ -86,10 +90,19 @@ export class DatasetLoadComponent implements OnInit { return true; } + refreshMyDatasets(selectedDatasetId: string | null) { + this.datasets.getMyDatasets().subscribe((datasets) => { + this.myDatasets = datasets.reverse(); + this.showMyDatasets = true; + this.selectedDataset = this.myDatasets.filter(x => x._id == selectedDatasetId)[0]; + this.resetSelectedDataset(); + }); + } + ngOnInit(): void { if (this.signalRService.hubConnection) { - this.signalRService.hubConnection.on("NotifyDataset", _ => { - this.refreshMyDatasets(); + this.signalRService.hubConnection.on("NotifyDataset", (dName: string, dId: string) => { + this.refreshMyDatasets(dId); }); } else { console.warn("Dataset-Load: No connection!"); diff --git a/frontend/src/app/_elements/item-predictor/item-predictor.component.html b/frontend/src/app/_elements/item-predictor/item-predictor.component.html index 7ae26fd3..3199dcc8 100644 --- a/frontend/src/app/_elements/item-predictor/item-predictor.component.html +++ b/frontend/src/app/_elements/item-predictor/item-predictor.component.html @@ -1,26 +1,35 @@ <div class="card" style="min-width: 12rem;"> - <div class="card-header"> - {{predictor.name}} + <div class="card-header d-flex mb-2 justify-content-" style="padding: 0;margin: 0;"> + + <div class=" p-2 float-left "><b style="color: gray;">Prediktor</b></div> + </div> - <div class="card-body"> + <div class="card-body overflow-hidden"> + <b style="color: gray;">Opis</b><hr style="width: 20%;"> <p class="card-text"> - {{predictor.description}} + {{predictor.description}} </p> - <div class="d-flex flex-column align-items-center"> - <table class="table table-bordered table-sm"> + + <b style="color: gray;">Ulazne kolone</b> + <div style="overflow: scroll; overflow-y: hidden;"> + + <table class="table table-bordered table-md" > <thead> - <th class="text-center" *ngFor="let column of predictor.inputs">{{column}}</th> + <th scope="col" *ngFor="let column of predictor.inputs" >{{column}}</th> </thead> </table> - <mat-icon>arrow_downward</mat-icon> - <p> - {{predictor.output}} - </p> + </div> + <b style="color: gray;">Izlazna kolona: </b><b>{{predictor.output}}</b> + <hr> + <div> + <table> + <tr><td><span class="material-icons">calendar_today</span></td><td><span style="color: grey;"> <b> Kreirano</b></span></td><td>{{predictor.dateCreated |date}}</td></tr> + </table> </div> </div> <div class="card-footer text-center"> - <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" + <button class="btn btn-md col-4" style="background-color:#003459; color:white;" (click)="openPredictor();">Iskoristi</button> - <!--<a routerLink="/predict" mat-raised-button color="primary">Iskoristi</a>--> + </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/line-chart/line-chart.component.css b/frontend/src/app/_elements/line-chart/line-chart.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_elements/line-chart/line-chart.component.css diff --git a/frontend/src/app/_elements/line-chart/line-chart.component.html b/frontend/src/app/_elements/line-chart/line-chart.component.html new file mode 100644 index 00000000..c8f406f4 --- /dev/null +++ b/frontend/src/app/_elements/line-chart/line-chart.component.html @@ -0,0 +1,5 @@ +<div class="chart-wrapper"> + <canvas id="myChart"> + + </canvas> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/line-chart/line-chart.component.spec.ts b/frontend/src/app/_elements/line-chart/line-chart.component.spec.ts new file mode 100644 index 00000000..0c5e7ef5 --- /dev/null +++ b/frontend/src/app/_elements/line-chart/line-chart.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LineChartComponent } from './line-chart.component'; + +describe('LineChartComponent', () => { + let component: LineChartComponent; + let fixture: ComponentFixture<LineChartComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ LineChartComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(LineChartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_elements/line-chart/line-chart.component.ts b/frontend/src/app/_elements/line-chart/line-chart.component.ts new file mode 100644 index 00000000..1a8579a0 --- /dev/null +++ b/frontend/src/app/_elements/line-chart/line-chart.component.ts @@ -0,0 +1,88 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { Chart } from 'chart.js'; + +@Component({ + selector: 'app-line-chart', + templateUrl: './line-chart.component.html', + styleUrls: ['./line-chart.component.css'] +}) + +export class LineChartComponent implements OnInit { + + dataAcc: number[] = []; + dataMAE: number[] = []; + dataMSE: number[] = []; + dataLOSS: number[] = []; + + dataEpoch: number[] = []; + + constructor() { + /*let i = 0; + setInterval(() => { + this.dataAcc.push(0.5); + this.dataEpoch.push(i); + i++; + this.update(); + }, 200);*/ + } + + myChart!: Chart; + + update(myEpochs: number[], myAcc: number[], myLoss: number[], myMae: number[], myMse: number[]) { + this.dataAcc.length = 0; + this.dataAcc.push(...myAcc); + + this.dataEpoch.length = 0; + this.dataEpoch.push(...myEpochs); + + this.dataMAE.length = 0; + this.dataMAE.push(...myMae); + + this.dataLOSS.length = 0; + this.dataLOSS.push(...myLoss); + + this.dataMSE.length = 0; + this.dataMSE.push(...myMse); + + this.myChart.update(); + } + + ngOnInit(): void { + this.myChart = new Chart("myChart", + { + type: 'line', + data: { + labels: this.dataEpoch, + datasets: [{ + label: 'Accuracy', + data: this.dataAcc, + borderWidth: 1 + }, + { + label: 'Loss', + data: this.dataLOSS, + borderWidth: 1 + }, + { + label: 'MAE', + data: this.dataMAE, + borderWidth: 1 + }, + { + label: 'MSE', + data: this.dataMSE, + borderWidth: 1 + } + ] + }, + options: { + scales: { + y: { + beginAtZero: true + } + } + } + } + ); + } +} diff --git a/frontend/src/app/_elements/metric-view/metric-view.component.css b/frontend/src/app/_elements/metric-view/metric-view.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_elements/metric-view/metric-view.component.css diff --git a/frontend/src/app/_elements/metric-view/metric-view.component.html b/frontend/src/app/_elements/metric-view/metric-view.component.html new file mode 100644 index 00000000..e7a4c547 --- /dev/null +++ b/frontend/src/app/_elements/metric-view/metric-view.component.html @@ -0,0 +1,5 @@ +<div> + <app-line-chart> + + </app-line-chart> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/metric-view/metric-view.component.spec.ts b/frontend/src/app/_elements/metric-view/metric-view.component.spec.ts new file mode 100644 index 00000000..c3ecc67f --- /dev/null +++ b/frontend/src/app/_elements/metric-view/metric-view.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MetricViewComponent } from './metric-view.component'; + +describe('MetricViewComponent', () => { + let component: MetricViewComponent; + let fixture: ComponentFixture<MetricViewComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MetricViewComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MetricViewComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_elements/metric-view/metric-view.component.ts b/frontend/src/app/_elements/metric-view/metric-view.component.ts new file mode 100644 index 00000000..9193a0e5 --- /dev/null +++ b/frontend/src/app/_elements/metric-view/metric-view.component.ts @@ -0,0 +1,49 @@ +import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { SignalRService } from 'src/app/_services/signal-r.service'; +import { LineChartComponent } from '../line-chart/line-chart.component'; +@Component({ + selector: 'app-metric-view', + templateUrl: './metric-view.component.html', + styleUrls: ['./metric-view.component.css'] +}) +export class MetricViewComponent implements OnInit { + @ViewChild(LineChartComponent) linechartComponent!: LineChartComponent; + + @Input() history!: any[]; + + constructor(private signalRService: SignalRService) { } + + ngOnInit(): void { + } + + update(history: any[]) { + const myAcc: number[] = []; + const myMae: number[] = []; + const myMse: number[] = []; + const myLoss: number[] = []; + + const myEpochs: number[] = []; + this.history = history; + this.history.forEach((metrics, epoch) => { + myEpochs.push(epoch + 1); + for (let key in metrics) { + let value = metrics[key]; + console.log(key, ':::', value, epoch); + if (key === 'accuracy') { + myAcc.push(parseFloat(value)); + } + else if (key === 'loss') { + myLoss.push(parseFloat(value)); + } + else if (key === 'mae') { + myMae.push(parseFloat(value)); + } + else if (key === 'mse') { + myMse.push(parseFloat(value)); + } + } + }); + + this.linechartComponent.update(myEpochs, myAcc, myLoss, myMae, myMse); + } +}
\ No newline at end of file diff --git a/frontend/src/app/_elements/model-load/model-load.component.html b/frontend/src/app/_elements/model-load/model-load.component.html index f40ea476..1f9852d1 100644 --- a/frontend/src/app/_elements/model-load/model-load.component.html +++ b/frontend/src/app/_elements/model-load/model-load.component.html @@ -1,12 +1,10 @@ <div> <div class="d-flex flex-row justify-content-center align-items-center mt-3 mb-5"> - <button type="button" id="btnMyModel" class="btn" (click)="viewMyModelsForm()" - [ngClass]="{'btnType1': showMyModels, 'btnType2': !showMyModels}"> + <button type="button" id="btnMyModel" class="btn" (click)="viewMyModelsForm()" [ngClass]="{'btnType1': showMyModels, 'btnType2': !showMyModels}"> Izaberite model iz kolekcije </button> <h3 class="mt-3 mx-3">ili</h3> - <button type="button" id="btnNewModel" class="btn" (click)="viewNewModelForm()" - [ngClass]="{'btnType1': !showMyModels, 'btnType2': showMyModels}"> + <button type="button" id="btnNewModel" class="btn" (click)="viewNewModelForm()" [ngClass]="{'btnType1': !showMyModels, 'btnType2': showMyModels}"> Dodajte novi model </button> </div> @@ -17,8 +15,7 @@ <div *ngIf="showMyModels" class="px-5"> <div class="overflow-auto" style="max-height: 500px;"> <ul class="list-group"> - <li class="list-group-item p-3" *ngFor="let model of myModels|filter:term" - [ngClass]="{'selectedModelClass': this.selectedModel == model}"> + <li class="list-group-item p-3" *ngFor="let model of myModels|filter:((forExperiment != undefined) ? forExperiment.type : '')" [ngClass]="{'selectedModelClass': this.selectedModel == model}"> <app-item-model name="usersModel" [model]="model" (click)="selectThisModel(model);"> </app-item-model> </li> @@ -43,11 +40,7 @@ <textarea class="form-control" name="desc" rows="3" [(ngModel)]="newModel.description"></textarea> </div> </div> - <div class="col-2"> - <label for="dateCreated" class="col-form-label">Datum:</label> - <input type="text" class="form-control-plaintext" id="dateCreated" placeholder="--/--/--" - value="{{newModel.dateCreated | date: 'dd/MM/yyyy'}}" readonly> - </div> + </div> <h2 class="mt-5 mb-4 mx-5">Parametri treniranja modela:</h2> <div> @@ -58,8 +51,7 @@ <label for="type" class="col-form-label">Tip problema: </label> </div> <div class="col-2"> - <select id=typeOptions class="form-control" name="type" [(ngModel)]="newModel.type" - (change)="filterOptions()"> + <select id="typeOptions" class="form-select" name="type" [(ngModel)]="newModel.type" (change)="filterOptions()"> <option *ngFor="let option of Object.keys(ProblemType); let optionName of Object.values(ProblemType)" [value]="option"> @@ -72,10 +64,7 @@ <label for="hiddenLayers" class="col-form-label">Broj skrivenih slojeva: </label> </div> <div class="col-1"> - <input type="number" min="1" class="form-control" name="hiddenLayers" - [(ngModel)]="newModel.hiddenLayers" - (change)="newModel.hiddenLayerActivationFunctions = [].constructor(newModel.hiddenLayers).fill(newModel.hiddenLayerActivationFunctions[0])" - (ngModelChange)="updateGraph()"> + <input type="number" min="1" class="form-control" name="hiddenLayers" [(ngModel)]="newModel.hiddenLayers" (change)="newModel.hiddenLayerActivationFunctions = [].constructor(newModel.hiddenLayers).fill(newModel.hiddenLayerActivationFunctions[0])" (ngModelChange)="updateGraph()"> </div> </div> @@ -86,7 +75,7 @@ <label for="optimizer" class="col-form-label">Optimizacija: </label> </div> <div class="col-2"> - <select id=optimizerOptions class="form-control" name="optimizer" [(ngModel)]="newModel.optimizer"> + <select id="optimizerOptions" class="form-select" name="optimizer" [(ngModel)]="newModel.optimizer"> <option *ngFor="let option of Object.keys(Optimizer); let optionName of Object.values(Optimizer)" [value]="option"> @@ -100,19 +89,17 @@ <label for="hiddenLayerNeurons" class="col-form-label">Broj neurona skrivenih slojeva: </label> </div> <div class="col-1"> - <input type="number" min="1" class="form-control" name="hiddenLayerNeurons" - [(ngModel)]="newModel.hiddenLayerNeurons" (ngModelChange)="updateGraph()"> + <input type="number" min="1" class="form-control" name="hiddenLayerNeurons" [(ngModel)]="newModel.hiddenLayerNeurons" (ngModelChange)="updateGraph()"> </div> </div> <div class="row p-2"> <div class="col-1"></div> <div class="col-3"> - <label for="lossFunction" class="col-form-label">Funkcija obrade gubitka: </label> + <label for="lossFunction" class="col-form-label">Funkcija troška: </label> </div> <div class="col-2"> - <select id=lossFunctionOptions class="form-control" name="lossFunction" - [(ngModel)]="newModel.lossFunction" aria-checked="true"> + <select id="lossFunctionOptions" class="form-select" name="lossFunction" [(ngModel)]="newModel.lossFunction" aria-checked="true"> <option *ngFor="let option of Object.keys(lossFunction); let optionName of Object.values(lossFunction)" [value]="option"> @@ -122,10 +109,22 @@ </div> <div class="col-1"></div> <div class="col-3"> - <label for="batchSize" class="col-form-label">Broj uzorka po iteraciji: </label> + <label for="batchSize" class="col-form-label">Broj uzorka po iteraciji: <b>{{newModel.batchSize}}</b><br>(izaberite stepen dvojke)</label> </div> <div class="col-1"> - <input type="number" min="1" class="form-control" name="batchSize" [(ngModel)]="newModel.batchSize"> + + <input type="number" min="0" step="1" max="7" class="form-control" name="batchSizePower" [(ngModel)]="batchSizePower" (click)="updateBatchSize()"> + + </div> + + <div class="row p-2"> + <div class="col-1"></div> + <div class="col-3 m-1"> + <label for="epochs" class="col-form-label">Broj epoha: </label> + </div> + <div class="col-1"> + <input type="number" min="1" max="1000" class="form-control" name="epochs" [(ngModel)]="newModel.epochs"> + </div> </div> </div> @@ -138,8 +137,7 @@ <div class="row p-2" style="align-self: center;"> <div class="col-1"></div> <div class="col-3"> - <label for="hiddenLayerActivationFunction" class="col-form-label" - style="text-align: center;">Funkcija aktivacije<br>skrivenih slojeva:</label> + <label for="hiddenLayerActivationFunction" class="col-form-label" style="text-align: center;">Funkcija aktivacije<br>skrivenih slojeva:</label> </div> <div class="col-2 mt-2"> <div *ngFor="let item of [].constructor(newModel.hiddenLayers); let i = index"> @@ -147,8 +145,7 @@ <div class="input-group-prepend"> <span class="input-group-text">#{{i+1}}</span> </div> - <select [id]="'hiddenLayerActivationFunctionOption_'+i" class="form-control" - [(ngModel)]="newModel.hiddenLayerActivationFunctions[i]"> + <select [id]="'hiddenLayerActivationFunctionOption_'+i" class="form-select" [(ngModel)]="newModel.hiddenLayerActivationFunctions[i]"> <option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> @@ -160,12 +157,10 @@ </div> <div class="col-1"></div> <div class="col-2"> - <label for="outputLayerActivationFunction" class="col-form-label" - style="text-align: center;">Funkcija aktivacije<br>izlaznog sloja:</label> + <label for="outputLayerActivationFunction" class="col-form-label" style="text-align: center;">Funkcija aktivacije<br>izlaznog sloja:</label> </div> <div class="col-2 mt-2"> - <select id=outputLayerActivationFunctionOptions class="form-control" - name="outputLayerActivationFunction" [(ngModel)]="newModel.outputLayerActivationFunction"> + <select id="outputLayerActivationFunctionOptions" class="form-select" name="outputLayerActivationFunction" [(ngModel)]="newModel.outputLayerActivationFunction"> <option *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" [value]="option"> @@ -178,26 +173,23 @@ </div> </div> - <div class="form-check form-check-inline overflow-auto m-4" style="width: max-content;"> + <!--<div class="form-check form-check-inline overflow-auto m-4" style="width: max-content;"> <h3>Izaberite metrike:</h3> <div id="divMetricsinput" class="mt-2 mx-5"> - <div *ngFor="let option of Object.keys(metrics); let optionName of Object.values(metrics) " - class="form-check form-check-inline"> + <div *ngFor="let option of Object.keys(metrics); let optionName of Object.values(metrics) " class="form-check form-check-inline"> - <input name="cbmetrics" class="form-check-input" type="checkbox" value="{{option}}" - id="metrics_{{option}}" style="float: left;" checked> + <input name="cbmetrics" class="form-check-input" type="checkbox" value="{{option}}" id="metrics_{{option}}" style="float: left;" checked> <label class="form-check-label" for="metrics_{{option}}" for="inlineCheckbox2"> {{optionName}} </label> </div> </div> - </div> - + </div>--> + <div class="form-group row mt-3 mb-3"> <div class="col"></div> - <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" - (click)="uploadModel();">Sačuvaj + <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" (click)="uploadModel();">Sačuvaj model</button> <div class="col"></div> </div> diff --git a/frontend/src/app/_elements/model-load/model-load.component.ts b/frontend/src/app/_elements/model-load/model-load.component.ts index 745dc12e..fb4b3fd0 100644 --- a/frontend/src/app/_elements/model-load/model-load.component.ts +++ b/frontend/src/app/_elements/model-load/model-load.component.ts @@ -1,7 +1,10 @@ -import { Component, OnInit, ViewChild, Output, EventEmitter } from '@angular/core'; +import { Component, OnInit, ViewChild, Output, EventEmitter, Input } from '@angular/core'; import Shared from 'src/app/Shared'; +import Experiment from 'src/app/_data/Experiment'; import Model, { ActivationFunction, LossFunction, LossFunctionBinaryClassification, LossFunctionMultiClassification, LossFunctionRegression, Metrics, MetricsBinaryClassification, MetricsMultiClassification, MetricsRegression, NullValueOptions, Optimizer, ProblemType } from 'src/app/_data/Model'; +import { AuthService } from 'src/app/_services/auth.service'; import { ModelsService } from 'src/app/_services/models.service'; +import { SignalRService } from 'src/app/_services/signal-r.service'; import { GraphComponent } from '../graph/graph.component'; @@ -13,6 +16,7 @@ import { GraphComponent } from '../graph/graph.component'; export class ModelLoadComponent implements OnInit { @ViewChild(GraphComponent) graph!: GraphComponent; + @Input() forExperiment?: Experiment; @Output() selectedModelChangeEvent = new EventEmitter<Model>(); newModel: Model = new Model(); @@ -29,21 +33,44 @@ export class ModelLoadComponent implements OnInit { shared = Shared; term: string = ""; - selectedProblemType: string = ''; selectedMetrics = []; lossFunction: any = LossFunction; showMyModels: boolean = true; - constructor(private modelsService: ModelsService) { + batchSizePower: number = 2; + + constructor(private modelsService: ModelsService, private authService: AuthService) { + //console.log("forExperiment = ", this.forExperiment); + this.fetchModels(); + + this.authService.loggedInEvent.subscribe(_ => { + this.fetchModels(); + }) + } + + fetchModels(andSelectWithId: string | null = '') { + //if (this.forExperiment == undefined) { this.modelsService.getMyModels().subscribe((models) => { - this.myModels = models; + this.myModels = models.reverse(); + this.selectThisModel(this.myModels.filter(x => x._id == andSelectWithId)[0]); }); + /*} + else { + this.modelsService.getMyModelsByType(ProblemType.Regression).subscribe((models) => { + this.myModels = models; + //console.log("modeli po tipu: ", this.myModels); + }); + }*/ } ngOnInit(): void { } + updateBatchSize() { + this.newModel.batchSize = 2 ** this.batchSizePower; + } + updateGraph() { this.graph.update(); } @@ -62,12 +89,17 @@ export class ModelLoadComponent implements OnInit { uploadModel() { this.getMetrics(); - this.newModel.username = Shared.username; + this.newModel.uploaderId = Shared.userId; this.modelsService.addModel(this.newModel).subscribe((response) => { - Shared.openDialog('Model dodat', 'Model je uspešno dodat u bazu.'); - // treba da se selektuje nov model u listi modela - //this.selectedModel = + console.log(this.newModel); + //Shared.openDialog('Model dodat', 'Model je uspešno dodat u bazu.'); + + Shared.openYesNoDialog("Model dodat", "Model je uspešno dodat u bazu. Da li želite da nastavite treniranje sa dodatim modelom?", () => { + this.fetchModels(response._id); + this.showMyModels = true; + }); + this.fetchModels(); }, (error) => { Shared.openDialog('Greška', 'Model sa unetim nazivom već postoji u Vašoj kolekciji. Promenite naziv modela i nastavite sa kreiranim datasetom.'); }); diff --git a/frontend/src/app/_elements/navbar/navbar.component.html b/frontend/src/app/_elements/navbar/navbar.component.html index 7d0c4cd8..1988b834 100644 --- a/frontend/src/app/_elements/navbar/navbar.component.html +++ b/frontend/src/app/_elements/navbar/navbar.component.html @@ -6,31 +6,25 @@ </a> <ul class="nav col-12 col-lg-auto me-lg-auto mb-2 justify-content-center mb-md-0"> - <li><a routerLink="" class="nav-link px-2" - [class]="(currentUrl === '') ? 'text-secondary' : 'text-white'">Početna</a></li> - <li><a routerLink="experiment" class="nav-link px-2" - [class]="(currentUrl === '/experiment') ? 'text-secondary' : 'text-white'">Napravi eksperiment</a> + <li><a routerLink="" class="nav-link px-2" [class]="(currentUrl === '') ? 'text-secondary' : 'text-white'">Početna</a></li> + <li><a routerLink="experiment" class="nav-link px-2" [class]="(currentUrl === '/experiment') ? 'text-secondary' : 'text-white'">Napravi eksperiment</a> </li> - <li><a routerLink="training" class="nav-link px-2" - [class]="(currentUrl === '/training') ? 'text-secondary' : 'text-white'">Treniraj model</a> + <li><a routerLink="training" class="nav-link px-2" [class]="(currentUrl === '/training') ? 'text-secondary' : 'text-white'">Treniraj model</a> </li> - <li><a routerLink="my-predictors" class="nav-link px-2" - [class]="(currentUrl === '/my-predictors') ? 'text-secondary' : 'text-white' + (shared.loggedIn) ? '' : 'disabled'">Predvidi</a> + <li><a routerLink="my-predictors" class="nav-link px-2" [class]="(currentUrl === '/my-predictors') ? 'text-secondary' : 'text-white' + (shared.loggedIn) ? '' : 'disabled'">Predvidi</a> </li> </ul> <div *ngIf="shared.loggedIn" class="dropdown text-end"> - <a href="#" class="d-block link-light text-decoration-none dropdown-toggle" id="dropdownUser1" - data-bs-toggle="dropdown" aria-expanded="false"> - <img [src]="'/assets/profilePictures/'+ shared.photoId +'.png'" alt="mdo" width="32" height="32" - class="rounded-circle"> + <a href="#" class="d-block link-light text-decoration-none dropdown-toggle" id="dropdownUser1" data-bs-toggle="dropdown" aria-expanded="false"> + <img [src]="'/assets/profilePictures/'+ shared.photoId +'.png'" alt="mdo" width="32" height="32" class="rounded-circle"> </a> - <ul class="dropdown-menu text-small" aria-labelledby="dropdownUser1" - style="position: absolute; inset: 0px 0px auto auto; margin: 0px; transform: translate(0px, 34px);" - data-popper-placement="bottom-end"> - <li><a class="dropdown-item" routerLink="add-model">Nov model...</a></li> - <li><a class="dropdown-item" routerLink="settings">Podešavanja</a></li> + <ul class="dropdown-menu text-small" aria-labelledby="dropdownUser1" style="position: absolute; inset: 0px 0px auto auto; margin: 0px; transform: translate(0px, 34px);" data-popper-placement="bottom-end"> + <li><a class="dropdown-item" routerLink="my-datasets">Moji izvori podataka</a></li> + <li><a class="dropdown-item" routerLink="my-models">Moji modeli</a></li> + <li><a class="dropdown-item" routerLink="my-predictors">Moji prediktori</a></li> <li><a class="dropdown-item" routerLink="profile">Moj profil</a></li> + <li><a class="dropdown-item" routerLink="settings" disabled>Podešavanja</a></li> <li> <hr class="dropdown-divider"> </li> @@ -38,12 +32,10 @@ </ul> </div> <div *ngIf="!shared.loggedIn" class="dropdown text-end"> - <button type="button" mat-raised-button color="primary" class="mx-2" data-bs-toggle="modal" - data-bs-target="#modalForLogin"> + <button type="button" mat-raised-button color="primary" class="mx-2" data-bs-toggle="modal" data-bs-target="#modalForLogin"> Prijavi se </button> - <button type="button" mat-raised-button color="primary" data-bs-toggle="modal" - data-bs-target="#modalForRegister"> + <button type="button" mat-raised-button color="primary" data-bs-toggle="modal" data-bs-target="#modalForRegister"> Registruj se </button> </div> diff --git a/frontend/src/app/_elements/navbar/navbar.component.ts b/frontend/src/app/_elements/navbar/navbar.component.ts index 368508ed..d5d1744f 100644 --- a/frontend/src/app/_elements/navbar/navbar.component.ts +++ b/frontend/src/app/_elements/navbar/navbar.component.ts @@ -4,6 +4,7 @@ import { AuthService } from '../../_services/auth.service'; import shared from 'src/app/Shared'; import { UserInfoService } from 'src/app/_services/user-info.service'; import { MatDialog } from '@angular/material/dialog'; +import { SignalRService } from 'src/app/_services/signal-r.service'; @Component({ selector: 'app-navbar', @@ -15,11 +16,15 @@ export class NavbarComponent implements OnInit { currentUrl: string; shared = shared; - constructor(public location: Location, private auth: AuthService, private userInfoService: UserInfoService, private matDialog: MatDialog) { + constructor(public location: Location, private auth: AuthService, private userInfoService: UserInfoService, private matDialog: MatDialog, private signalRService: SignalRService) { shared.dialog = matDialog; this.currentUrl = this.location.path(); this.location.onUrlChange(() => { this.currentUrl = this.location.path(); + }); + + this.auth.loggedInEvent.subscribe(_ => { + this.signalRService.startConnection(); }) } @@ -34,5 +39,6 @@ export class NavbarComponent implements OnInit { logOut() { this.auth.logOut(); + this.signalRService.stopConnection(); } } diff --git a/frontend/src/app/_elements/notifications/notifications.component.html b/frontend/src/app/_elements/notifications/notifications.component.html index ef897cfc..3b2f4eaa 100644 --- a/frontend/src/app/_elements/notifications/notifications.component.html +++ b/frontend/src/app/_elements/notifications/notifications.component.html @@ -11,14 +11,18 @@ </button> </h2> - <div id="collapseNotifs" class="collapse show"> + <div id="collapseNotifs" class="collapse show overflow-auto" style="max-height: 32rem;"> <div *ngFor="let notification of notifications" class="p-2 m-1 border rounded"> - <div class="d-flex flex-row"> - <p>{{notification.title}}</p> - </div> - <div *ngIf="notification.hasProgress" class="border-3 border-primary bg-dark m-1" style="height: 5px; margin-top: -10px !important;"> - <div class="bg-primary" style="height: 5px;" [style]="'width: '+(notification.progress*100)+'%;'"> + <div class="d-flex flex-row "> + <div> + <p>{{notification.title}}</p> + <div *ngIf="notification.hasProgress" class="border-3 border-primary bg-dark m-1" style="height: 5px; margin-top: -10px !important; min-width: 12rem;"> + <div class="bg-primary" style="height: 5px;" [style]="'width: '+(notification.progress*100)+'%;'"> + </div> + </div> </div> + <button type="button" class="btn-close ms-auto align-self-center" aria-label="Close" (click)="removeNotification(notification);"></button> </div> + </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/notifications/notifications.component.ts b/frontend/src/app/_elements/notifications/notifications.component.ts index e199f70a..f324662a 100644 --- a/frontend/src/app/_elements/notifications/notifications.component.ts +++ b/frontend/src/app/_elements/notifications/notifications.component.ts @@ -21,13 +21,33 @@ export class NotificationsComponent implements OnInit { this.notifications.push(new Notification(`Obrađen izvor podataka: ${dName}`, dId, 1.0, false)); }); - this.signalRService.hubConnection.on("NotifyEpoch", (epoch: string, mName: string, mId: string, numEpochs) => { - //todo epoch - this.notifications.push(new Notification(`Treniranje modela: ${mName}`, mId, 0.5)); + this.signalRService.hubConnection.on("NotifyEpoch", (mName: string, mId: string, stat: string, totalEpochs: number, currentEpoch: number) => { + const existingNotification = this.notifications.find(x => x.id === mId) + const progress = ((currentEpoch + 1) / totalEpochs); + //console.log("Ukupno epoha", totalEpochs, "Trenutna epoha:", currentEpoch); + if (!existingNotification) + this.notifications.push(new Notification(`Treniranje modela: ${mName}`, mId, progress, true)); + else { + existingNotification.progress = progress; + } + }); + + this.signalRService.hubConnection.on("NotifyModel", (mName: string, mId: string, stat: string, totalEpochs: number, currentEpoch: number) => { + const existingNotification = this.notifications.find(x => x.id === mId) + const progress = ((currentEpoch + 1) / totalEpochs); + if (!existingNotification) + this.notifications.push(new Notification(`Treniranje modela: ${mName}`, mId, progress, true)); + else { + existingNotification.progress = progress; + } }); } else { console.warn("Notifications: No connection!"); } } + removeNotification(notification: Notification) { + this.notifications.splice(this.notifications.indexOf(notification), 1); + } + } diff --git a/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.html b/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.html index 06e74093..572e8c4c 100644 --- a/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.html +++ b/frontend/src/app/_modals/yes-no-dialog/yes-no-dialog.component.html @@ -1,8 +1,8 @@ <h2 mat-dialog-title class="text-muted">{{data.title}}</h2> <div mat-dialog-content class="mt-4" style="color: rgb(81, 76, 76);"> - {{data.message}} + {{data.message}} </div> <div mat-dialog-actions class="d-flex justify-content-center mt-4"> - <button mat-button cdkFocusInitial (click)="onYesClick()" style="background-color: lightgray;">Da</button> - <button mat-button cdkFocusInitial (click)="onNoClick()" style="background-color: lightgray;">Ne</button> + <button mat-button cdkFocusInitial (click)="onYesClick()" class="btn-lg" style="background-color: #003459; color:white;">Da</button> + <button mat-button cdkFocusInitial (click)="onNoClick()" class="btn-lg" style="background-color: lightgray;">Ne</button> </div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts b/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts index c83bf208..66b3755e 100644 --- a/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts +++ b/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts @@ -33,7 +33,7 @@ export class FilterDatasetsComponent implements OnInit { newDataset._id = ""; newDataset.isPublic = false; newDataset.lastUpdated = new Date(); - newDataset.username = decodedToken.name; + newDataset.uploaderId = decodedToken.uploaderId; let name=prompt("Unesite naziv dataset-a",newDataset.name); newDataset.name=name as string; if(name!=null && name!="") diff --git a/frontend/src/app/_pages/home/home.component.html b/frontend/src/app/_pages/home/home.component.html index 08f95a69..f5e94d27 100644 --- a/frontend/src/app/_pages/home/home.component.html +++ b/frontend/src/app/_pages/home/home.component.html @@ -53,4 +53,5 @@ <app-carousel [items]="publicPredictors" [type]="'Predictor'"> </app-carousel> <h3><a routerLink="browse-predictors">Pogledaj sve javne trenirane modele...</a></h3> + </div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/my-models/my-models.component.html b/frontend/src/app/_pages/my-models/my-models.component.html index b0e9c4ef..9b281239 100644 --- a/frontend/src/app/_pages/my-models/my-models.component.html +++ b/frontend/src/app/_pages/my-models/my-models.component.html @@ -15,7 +15,7 @@ <div class="panel-footer row"><!-- panel-footer --> <div class="col-xs-6 text-center"> <div> - <button type="button" class="btn btn-default btn-lg"style="min-width: 7rem;float: left;" (click)="deleteThisModel(model)" mat-raised-button color="primary">Koristi + <button type="button" class="btn btn-default btn-lg"style="min-width: 7rem;float: left;" (click)="useThisModel(model)" mat-raised-button color="primary">Koristi <span class="glyphicon glyphicon-search"></span> </button> <button (click)="deleteThisModel(model)" mat-raised-button color="warn" style="min-width: 7rem;float: right" ><mat-icon>delete</mat-icon></button> diff --git a/frontend/src/app/_pages/my-models/my-models.component.ts b/frontend/src/app/_pages/my-models/my-models.component.ts index 92d3fbaa..d379fa69 100644 --- a/frontend/src/app/_pages/my-models/my-models.component.ts +++ b/frontend/src/app/_pages/my-models/my-models.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; import shared from 'src/app/Shared'; import Model from 'src/app/_data/Model'; import { ModelsService } from 'src/app/_services/models.service'; @@ -12,7 +13,7 @@ export class MyModelsComponent implements OnInit { myModels: Model[] = []; //myModel: Model; - constructor(private modelsS : ModelsService) { + constructor(private modelsS : ModelsService, private router : Router) { @@ -44,6 +45,11 @@ deleteThisModel(model: Model): void{ } +useThisModel(model: Model): void{ + + this.router.navigate(['/training']) + +} getAllMyModels(): void{ this.modelsS.getMyModels().subscribe(m => { this.myModels = m; diff --git a/frontend/src/app/_pages/my-predictors/my-predictors.component.html b/frontend/src/app/_pages/my-predictors/my-predictors.component.html index d38f93e4..31fa786c 100644 --- a/frontend/src/app/_pages/my-predictors/my-predictors.component.html +++ b/frontend/src/app/_pages/my-predictors/my-predictors.component.html @@ -1,16 +1,23 @@ <div id="header"> <h1>Trenirani modeli</h1> </div> -<div id="container" style="background-color:rgba(255, 255, 255, 0.8);"> -<div class="row" *ngFor="let predictor of predictors"> - <div class="left"> - <app-item-predictor [predictor]="predictor"></app-item-predictor> - </div> - <div> - <button (click)="delete(predictor)" mat-raised-button color="warn" style="min-width: 15rem;float: right" ><mat-icon>delete</mat-icon></button> - </div> - +<div id="wrapper"> +<div id="container" class="container p-5" style="background-color:rgba(255, 255, 255, 0.8); min-height: 100%;"> + <div class="row mt-3 mb-2 d-flex justify-content-center"> + + <div class="col-sm-6" style="margin-bottom: 10px;"> + </div> + <div class="row"> + <div class="col-sm-4" style="margin-bottom: 10px;" *ngFor="let predictor of predictors"> + <app-item-predictor [predictor]="predictor"></app-item-predictor> + <div> + <button (click)="deleteThisPredictor(predictor)" mat-raised-button color="warn" style="min-width: 10rem;float: right" ><mat-icon>delete</mat-icon></button> + </div> + </div> + </div> +</div> </div> - </div> + + diff --git a/frontend/src/app/_pages/my-predictors/my-predictors.component.ts b/frontend/src/app/_pages/my-predictors/my-predictors.component.ts index 17c496fd..4dc5300d 100644 --- a/frontend/src/app/_pages/my-predictors/my-predictors.component.ts +++ b/frontend/src/app/_pages/my-predictors/my-predictors.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import Predictor from 'src/app/_data/Predictor'; import { PredictorsService } from 'src/app/_services/predictors.service'; - +import shared from 'src/app/Shared'; @Component({ selector: 'app-my-predictors', templateUrl: './my-predictors.component.html', @@ -12,28 +12,30 @@ export class MyPredictorsComponent implements OnInit { constructor(private predictorsS : PredictorsService) { } ngOnInit(): void { - this.getAllMyPredictors(); - + this.predictorsS.getMyPredictors().subscribe((response) => { + this.predictors = response; + }, (error) => { + if (error.error == "Predictor with...") { + shared.openDialog("Greska", "Greska"); + } + }); } - delete(predictor: Predictor){ - if(window.confirm("IZABRANI MODEL ĆE BITI IZBRISAN")) - { - this.predictorsS.deletePredictor(predictor).subscribe((response) => { - this.getAllMyPredictors(); - }, (error) =>{ - if (error.error == "Predictor with name = {name} deleted") { - alert("Greška pri brisanju modela!"); - } - }); - } - - + deleteThisPredictor(predictor: Predictor): void{ + shared.openYesNoDialog('Brisanje prediktora','Da li ste sigurni da želite da obrišete prediktor?',() => { + this.predictorsS.deletePredictor(predictor).subscribe((response) => { + this.getAllMyPredictors(); + }, (error) =>{ + if (error.error == "Predictor with name = {name} deleted") { + shared.openDialog("Obaveštenje", "Greška prilikom brisanja prediktora."); + } + }); + }); } getAllMyPredictors(): void{ - this.predictorsS.getMyPredictors().subscribe(m => { - this.predictors = m; + this.predictorsS.getMyPredictors().subscribe(p => { + this.predictors = p; }); } diff --git a/frontend/src/app/_services/auth.service.ts b/frontend/src/app/_services/auth.service.ts index 92cebe7f..9e3f9f2f 100644 --- a/frontend/src/app/_services/auth.service.ts +++ b/frontend/src/app/_services/auth.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@angular/core'; +import { EventEmitter, Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { JwtHelperService } from '@auth0/angular-jwt'; import { CookieService } from 'ngx-cookie-service'; @@ -12,6 +12,8 @@ const jwtHelper = new JwtHelperService(); }) export class AuthService { + public loggedInEvent: EventEmitter<boolean> = new EventEmitter(); + shared = shared; constructor(private http: HttpClient, private cookie: CookieService) { } @@ -44,6 +46,7 @@ export class AuthService { enableAutoRefresh() { this.lastToken = this.cookie.get('token'); + clearTimeout(this.refresher); let exp = jwtHelper.getTokenExpirationDate(this.lastToken); if (!exp) { exp = new Date(); @@ -51,6 +54,7 @@ export class AuthService { var property = jwtHelper.decodeToken(this.cookie.get('token')); var username = property['name']; if (username != "") { + this.refresher = setTimeout(() => { this.http.post(`${Configuration.settings.apiURL}/auth/renewJwt`, {}, { headers: this.authHeader(), responseType: 'text' }).subscribe((response) => { this.authenticate(response); @@ -79,6 +83,7 @@ export class AuthService { } this.cookie.set('token', token, exp); this.updateUser(); + this.loggedInEvent.emit(true); } updateUser() { diff --git a/frontend/src/app/_services/datasets.service.ts b/frontend/src/app/_services/datasets.service.ts index c3281be6..b2272f0a 100644 --- a/frontend/src/app/_services/datasets.service.ts +++ b/frontend/src/app/_services/datasets.service.ts @@ -29,10 +29,10 @@ export class DatasetsService { } editDataset(dataset: Dataset): Observable<Dataset> { - return this.http.put<Dataset>(`${Configuration.settings.apiURL}/dataset/`, dataset, { headers: this.authService.authHeader() }); + return this.http.put<Dataset>(`${Configuration.settings.apiURL}/dataset/` + dataset._id, dataset, { headers: this.authService.authHeader() }); } deleteDataset(dataset: Dataset) { - return this.http.delete(`${Configuration.settings.apiURL}/dataset/` + dataset.name, { headers: this.authService.authHeader(), responseType: "text" }); + return this.http.delete(`${Configuration.settings.apiURL}/dataset/` + dataset._id, { headers: this.authService.authHeader(), responseType: "text" }); } } diff --git a/frontend/src/app/_services/models.service.ts b/frontend/src/app/_services/models.service.ts index 44383828..1130b12c 100644 --- a/frontend/src/app/_services/models.service.ts +++ b/frontend/src/app/_services/models.service.ts @@ -1,6 +1,6 @@ import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; -import Model from '../_data/Model'; +import Model, { ProblemType } from '../_data/Model'; import { AuthService } from './auth.service'; import { Observable } from 'rxjs'; import Dataset from '../_data/Dataset'; @@ -31,20 +31,16 @@ export class ModelsService { addModel(model: Model): Observable<any> { return this.http.post(`${Configuration.settings.apiURL}/model/add`, model, { headers: this.authService.authHeader() }); } - addDataset(dataset: Dataset): Observable<any> { - return this.http.post(`${Configuration.settings.apiURL}/dataset/add`, dataset, { headers: this.authService.authHeader() }); - } trainModel(modelId: string, experimentId: string): Observable<any> { return this.http.post(`${Configuration.settings.apiURL}/model/trainmodel`, { ModelId: modelId, ExperimentId: experimentId }, { headers: this.authService.authHeader(), responseType: 'text' }); } - getMyDatasets(): Observable<Dataset[]> { - return this.http.get<Dataset[]>(`${Configuration.settings.apiURL}/dataset/mydatasets`, { headers: this.authService.authHeader() }); - } - getMyModels(): Observable<Model[]> { return this.http.get<Model[]>(`${Configuration.settings.apiURL}/model/mymodels`, { headers: this.authService.authHeader() }); } + getMyModelsByType(problemType: ProblemType): Observable<Model[]> { + return this.http.get<Model[]>(`${Configuration.settings.apiURL}/model/mymodelsbytype/` + problemType, { headers: this.authService.authHeader() }); + } editModel(model: Model): Observable<Model> { return this.http.put<Model>(`${Configuration.settings.apiURL}/model/`, model, { headers: this.authService.authHeader() }); @@ -53,4 +49,5 @@ export class ModelsService { deleteModel(model: Model) { return this.http.delete(`${Configuration.settings.apiURL}/model/` + model.name, { headers: this.authService.authHeader(), responseType: "text" }); } + } diff --git a/frontend/src/app/_services/predictors.service.ts b/frontend/src/app/_services/predictors.service.ts index 9e8383aa..a24ee708 100644 --- a/frontend/src/app/_services/predictors.service.ts +++ b/frontend/src/app/_services/predictors.service.ts @@ -24,7 +24,7 @@ export class PredictorsService { } deletePredictor(predictor: Predictor) { - return this.http.delete(`${Configuration.settings.apiURL}/predictor/` + predictor.name, { headers: this.authService.authHeader(), responseType: "text" }); + return this.http.delete(`${Configuration.settings.apiURL}/predictor/` + predictor._id, { headers: this.authService.authHeader(), responseType: "text" }); } getMyPredictors(): Observable<Predictor[]> { diff --git a/frontend/src/app/_services/signal-r.service.ts b/frontend/src/app/_services/signal-r.service.ts index b279b5ca..6a2e61e9 100644 --- a/frontend/src/app/_services/signal-r.service.ts +++ b/frontend/src/app/_services/signal-r.service.ts @@ -16,13 +16,18 @@ export class SignalRService { }).build(); this.hubConnection.on("Notify", (message: string) => { - console.log(" " + message); + //console.log(" " + message); }); this.hubConnection .start() - .then(() => console.log("con Started")) - .catch(err => console.log("Error" + err)) + .then(() => {}) + .catch(err => {}) } + + public stopConnection = () => { + this.hubConnection?.stop(); + } + constructor(private cookie: CookieService) { } } diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index e22f7a88..a0952d21 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -19,6 +19,7 @@ const routes: Routes = [ { path: '', component: HomeComponent, data: { title: 'Početna strana' } }, /*{ path: 'add-model', component: AddModelComponent, data: { title: 'Dodaj model' } },*/ { path: 'experiment', component: ExperimentComponent, data: { title: 'Dodaj eksperiment' } }, + { path: 'training/:id', component: TrainingComponent, data: { title: 'Treniraj model' } }, { path: 'training', component: TrainingComponent, data: { title: 'Treniraj model' } }, { path: 'my-datasets', component: MyDatasetsComponent, canActivate: [AuthGuardService], data: { title: 'Moji izvori podataka' } }, { path: 'my-models', component: MyModelsComponent, canActivate: [AuthGuardService], data: { title: 'Moji modeli' } }, @@ -28,6 +29,7 @@ const routes: Routes = [ { path: 'browse-datasets', component: FilterDatasetsComponent, data: { title: 'Javni izvori podataka' } }, { path: 'browse-predictors', component: BrowsePredictorsComponent, data: { title: 'Javni trenirani modeli' } }, { path: 'predict/:id', component: PredictComponent, data: { title: 'Predvidi vrednosti' } }, + ]; @NgModule({ diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 59f247ed..54c18bec 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -50,7 +50,6 @@ export class AppComponent implements OnInit { private startHttpRequest = () => { this.http.get('http://localhost:5283/chatHub') .subscribe(res => { - console.log(res); }) } diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index c4f89ad8..51374bd4 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -48,7 +48,11 @@ import { TrainingComponent } from './training/training.component'; import { ItemExperimentComponent } from './_elements/item-experiment/item-experiment.component'; import { YesNoDialogComponent } from './_modals/yes-no-dialog/yes-no-dialog.component'; import { Configuration } from './configuration.service'; +import { PointLinechartComponent } from './point-linechart/point-linechart.component'; +import { MixedChartComponent } from './mixed-chart/mixed-chart.component'; +import { LineChartComponent } from './_elements/line-chart/line-chart.component'; +import { MetricViewComponent } from './_elements/metric-view/metric-view.component'; export function initializeApp(appConfig: Configuration) { return () => appConfig.load(); } @@ -87,7 +91,14 @@ export function initializeApp(appConfig: Configuration) { GraphComponent, TrainingComponent, ItemExperimentComponent, - YesNoDialogComponent + YesNoDialogComponent, + LineChartComponent, + PointLinechartComponent, + MixedChartComponent, + LineChartComponent, + MetricViewComponent, + + ], imports: [ BrowserModule, diff --git a/frontend/src/app/experiment/experiment.component.css b/frontend/src/app/experiment/experiment.component.css index 4a3d7741..d84a897e 100644 --- a/frontend/src/app/experiment/experiment.component.css +++ b/frontend/src/app/experiment/experiment.component.css @@ -40,4 +40,9 @@ ul li:hover { h2 { color: #003459; +} + +.boldClass { + font-weight: bold; + font-size: 125%; }
\ No newline at end of file diff --git a/frontend/src/app/experiment/experiment.component.html b/frontend/src/app/experiment/experiment.component.html index e46f5bd9..4bdd79b2 100644 --- a/frontend/src/app/experiment/experiment.component.html +++ b/frontend/src/app/experiment/experiment.component.html @@ -4,159 +4,147 @@ <div id="wrapper"> <div id="container" class="container p-5" style="background-color: white; min-height: 100%;"> - <div class="d-flex flex-row justify-content-center align-items-center my-3"> - <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="0">Izvor podataka</a> + <div class="d-flex flex-row justify-content-center align-items-center my-3 links"> + <a id="firstStep" href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="0" (click)="updateCarouselIndex(0);" [ngClass]="{'boldClass' : carouselIndex == 0}" style="text-decoration: none">Izvor podataka</a> <mat-icon>arrow_forward</mat-icon> - <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="1">Preprocesiranje</a> + <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="1" (click)="updateCarouselIndex(1)" [ngClass]="{'boldClass' : carouselIndex == 1}" style="text-decoration: none">Preprocesiranje</a> <mat-icon>arrow_forward</mat-icon> - <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="2">Dodaj eksperiment</a> + <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="2" (click)="updateCarouselIndex(2)" [ngClass]="{'boldClass' : carouselIndex == 2}" style="text-decoration: none">Dodaj eksperiment</a> </div> - <div id="carouselExampleControls" class="carousel slide px-5 mt-5" data-bs-wrap="false" data-bs-ride="carousel" data-bs-interval="false"> - <div class="carousel-inner"> - <div class="carousel-item active mt-2"> - <h2 class="mb-5">1. Izvor podataka</h2> - <app-dataset-load (selectedDatasetChangeEvent)="updateDataset($event)"></app-dataset-load> - </div> + <div id="carouselExampleControls" class="carousel px-5 mt-5" data-bs-wrap="false" data-bs-ride="carousel" data-bs-interval="false"> + <div class="carousel-inner"> + <div class="carousel-item active mt-2"> + <h2 class="mb-5">1. Izvor podataka</h2> + <app-dataset-load (selectedDatasetChangeEvent)="updateDataset($event)"></app-dataset-load> + </div> - <div class="carousel-item mt-2"> - <h2 class="mb-4">2. Preprocesiranje</h2> + <div class="carousel-item mt-2"> + <h2 class="mb-4">2. Preprocesiranje</h2> - <div class="px-5"> - <h3>Biranje ulaznih i izlaznih kolona:</h3> - <div *ngIf="selectedDataset"> - <div class="row"> - <div class="col d-flex justify-content-center"> - <h3>Izaberite ulazne kolone:</h3> - <div id="divInputs" class="form-check mt-2"> - <br> - <div *ngFor="let item of selectedDataset.columnInfo; let i = index"> - <input class="form-check-input" type="checkbox" value="{{item.columnName}}" - id="cb_{{item.columnName}}" name="cbsNew" - [checked]="experiment.outputColumn != item.columnName" - [disabled]="experiment.outputColumn == item.columnName" - (click)="checkedColumnsChanged(item, 0)"> - <label class="form-check-label" for="cb_{{item.columnName}}"> + <div class="px-5"> + <h3>Biranje ulaznih i izlaznih kolona:</h3> + <div *ngIf="selectedDataset"> + <div class="row"> + <div class="col d-flex justify-content-center"> + <h3>Izaberite ulazne kolone:</h3> + <div id="divInputs" class="form-check mt-2"> + <br> + <div *ngFor="let item of selectedDataset.columnInfo; let i = index"> + <input class="form-check-input" type="checkbox" value="{{item.columnName}}" id="cb_{{item.columnName}}" name="cbsNew" [checked]="experiment.outputColumn != item.columnName" [disabled]="experiment.outputColumn == item.columnName" (change)="checkedColumnsChanged(item, 0); resetColumnEncodings();"> + <label class="form-check-label" for="cb_{{item.columnName}}"> {{item.columnName}} </label> + </div> </div> </div> - </div> - <div class="col d-flex justify-content-left"> - <h3>Izaberite izlaznu kolonu:</h3> - <div id="divOutputs" class="form-check mt-2"> - <br> - <div *ngFor="let item of selectedDataset.columnInfo; let i = index"> - <input class="form-check-input" type="radio" value="{{item.columnName}}" - id="rb_{{item.columnName}}" name="rbsNew" - [(ngModel)]="this.experiment.outputColumn" - (change)="experiment.outputColumn = item.columnName" - (click)="checkedColumnsChanged(item, 1);"> - <label class="form-check-label" for="rb_{{item.columnName}}"> + <div class="col d-flex justify-content-left"> + <h3>Izaberite izlaznu kolonu:</h3> + <div id="divOutputs" class="form-check mt-2"> + <br> + <div *ngFor="let item of selectedDataset.columnInfo; let i = index"> + <input class="form-check-input" type="radio" value="{{item.columnName}}" id="rb_{{item.columnName}}" name="rbsNew" [(ngModel)]="this.experiment.outputColumn" (change)="experiment.outputColumn = item.columnName; checkedColumnsChanged(item, 1); resetColumnEncodings();" + checked> + <label class="form-check-label" for="rb_{{item.columnName}}"> {{item.columnName}} </label> + </div> </div> </div> </div> </div> - </div> - - <h3 class="mt-5">Popunjavanje nedostajućih vrednosti:</h3> - <div class="form-check" *ngIf="selectedDataset"> - <input type="radio" [(ngModel)]="experiment.nullValues" [value]="NullValueOptions.DeleteRows" - class="form-check-input" value="deleteRows" name="fillMissing" id="delRows" checked - data-bs-toggle="collapse" data-bs-target="#fillMissingCustom.show"> - <label for="delRows" class="form-check-label">Obriši sve - redove sa nedostajućim vrednostima ({{selectedDataset.nullRows}} od {{selectedDataset.rowCount}})</label><br> - <input type="radio" [(ngModel)]="experiment.nullValues" [value]="NullValueOptions.DeleteColumns" - class="form-check-input" value="deleteCols" name="fillMissing" id="delCols" data-bs-toggle="collapse" - data-bs-target="#fillMissingCustom.show"> - <label for="delCols" class="form-check-label">Obriši sve + + + <div class="mt-5 mb-3 mx-3" *ngIf="countSelectedNullCols() == 0"> + <h3 class="border p-2 text-center"><i>Izabrane kolone nemaju nedostajuće vrednosti koje možete popuniti.</i></h3> + </div> + + <div *ngIf="countSelectedNullCols() != 0"> + <h3 class="mt-5">Popunjavanje nedostajućih vrednosti:</h3> + <div class="form-check" *ngIf="selectedDataset"> + <input type="radio" [(ngModel)]="experiment.nullValues " [value]="NullValueOptions.DeleteRows " class="form-check-input" value="deleteRows" name="fillMissing " id="delRows" checked data-bs-toggle="collapse" data-bs-target="#fillMissingCustom.show"> + <label *ngIf="selectedColumnsInfoArray.length == selectedDataset.columnInfo.length" for="delRows" class="form-check-label ">Obriši sve + redove sa nedostajućim vrednostima ({{selectedDataset.nullRows}} od {{selectedDataset.rowCount}})</label> + <label *ngIf="selectedColumnsInfoArray.length != selectedDataset.columnInfo.length" for="delRows" class="form-check-label ">Obriši sve + redove sa nedostajućim vrednostima</label><br> + <input type="radio" [(ngModel)]="experiment.nullValues " [value]="NullValueOptions.DeleteColumns " class="form-check-input" value="deleteCols" name="fillMissing " id="delCols" data-bs-toggle="collapse" data-bs-target="#fillMissingCustom.show"> + <label for="delCols" class="form-check-label ">Obriši sve kolone sa nedostajućim vrednostima ({{countSelectedNullCols()}} od {{selectedDataset.columnInfo.length}})</label><br> - <input type="radio" [(ngModel)]="experiment.nullValues" [value]="NullValueOptions.Replace" - class="form-check-input" name="fillMissing" id="replace" data-bs-toggle="collapse" - data-bs-target="#fillMissingCustom:not(.show)"> - <label for="replace" class="form-check-label">Izabraću + <input type="radio" [(ngModel)]="experiment.nullValues " [value]="NullValueOptions.Replace " class="form-check-input" name="fillMissing" id="replace" data-bs-toggle="collapse" data-bs-target="#fillMissingCustom:not(.show)"> + <label for="replace" class="form-check-label">Izabraću vrednosti koje će da zamene nedostajuće vrednosti za svaku kolonu...</label><br><br> - <div class="collapse" id="fillMissingCustom"> - <div> - <label for="columnReplacers" class="form-label">Unesite zamenu za svaku kolonu:</label> - <div class="my-3" *ngIf="getSelectedColumnsArrayWithoutNullVals().length > 0" > - <label class="text-center form-control mx-3 text-secondary"> + <div class="collapse" id="fillMissingCustom"> + <div> + <label for="columnReplacers" class="form-label">Unesite zamenu za svaku kolonu:</label> + <div class="my-3 " *ngIf="getSelectedColumnsArrayWithoutNullVals().length> 0"> + <label class="text-center form-control mx-3 text-secondary"> Kolone <span style="font-style: italic;" *ngFor="let colname of getSelectedColumnsArrayWithoutNullVals(); let i = index"> <span *ngIf="i != getSelectedColumnsArrayWithoutNullVals().length - 1">{{colname}}, </span> <span *ngIf="i == getSelectedColumnsArrayWithoutNullVals().length - 1">{{colname}} </span> </span> nemaju nedostajućih vrednosti za popunjavanje. </label> - </div> - <div id="columnReplacers"> - <div *ngFor="let column of selectedColumnsInfoArray; let i = index" class="my-3"> - <div *ngIf="column.numNulls > 0"> - <span class="w-20 mx-3"> - {{column.columnName}} <span class="small" style="color:gray;">({{column.numNulls}} null) - </span> - </span> - - <label *ngIf="column.numNulls <= 0" - class="text-center form-control mx-3 text-secondary"> - Ova kolona nema - nedostajućih - vrednosti. - </label> - - <div *ngIf="column.numNulls > 0" class="d-flex flex-row justify-content-end"> - <div class="flex-grow-3 mx-3 me-auto"> - <div class="input-group"> - <div class="input-group-prepend"> - <label [for]="'fillCol_'+column.columnName" class="form-control"> + </div> + <div id="columnReplacers"> + <div *ngFor="let column of selectedColumnsInfoArray; let i = index" class="my-3"> + <div *ngIf="column.numNulls > 0"> + <span class="w-20 mx-3"> + {{column.columnName}} + <span class="small" style="color:gray;">({{column.numNulls}} null)</span> + </span> + + <div class="d-flex flex-row justify-content-end"> + <div class="flex-grow-3 mx-3 me-auto"> + <div class="input-group"> + <div class="input-group-prepend"> + <label [for]="'fillCol_'+column.columnName" class="form-control"> Zameni <input type="radio" [id]="'fillCol_'+column.columnName" [name]="'delOp_'+column.columnName"> </label> - </div> - <input type="text" class="form-control" [id]="'fillText_'+column.columnName" - (keyup)="checkFillColRadio(column.columnName)" - placeholder="Unesi vrednost..."> - - <div class="input-group-append"> - <select [id]="'replaceOptions'+i" class="form-control btn-primary" - *ngIf="column.isNumber" (change)="replace($event, column); checkFillColRadio(column.columnName);"> + </div> + <input type="text" class="form-control" [id]="'fillText_'+column.columnName" (keyup)="checkFillColRadio(column.columnName)" placeholder="Unesi vrednost..."> + + <div class="input-group-append"> + <select [id]="'replaceOptions'+i" class="form-control btn-primary" *ngIf="column.isNumber" (change)="replace($event, column); checkFillColRadio(column.columnName);"> <option *ngFor="let option of Object.keys(ReplaceWith); let optionName of Object.values(ReplaceWith)" [value]="option"> {{ optionName }} </option> </select> - <select [id]="'replaceOptions'+i" - class="form-control btn-outline-primary" - *ngIf="!column.isNumber && column.numNulls > 0" - (change)="replace($event, column); checkFillColRadio(column.columnName);"> + <select [id]="'replaceOptions'+i" class="form-control btn-outline-primary" *ngIf="!column.isNumber && column.numNulls > 0" (change)="replace($event, column); checkFillColRadio(column.columnName);"> <option *ngFor="let option of column.uniqueValues" [value]="option"> {{ option }} </option> </select> + </div> + </div> </div> - </div> - </div> - - <div class="flex-shrink-1 mx-3"> - <div class="input-group"> - <label class="form-control" [for]="'delCol_'+column.columnName">Izbriši - kolonu - <input type="radio" [id]="'delCol_'+column.columnName" + + <div class="flex-shrink-1 mx-3"> + <div class="input-group" *ngIf="column.columnName != this.experiment.outputColumn"> + <label class="form-control" [for]="'delCol_'+column.columnName">Izbriši + kolonu + <input type="radio" [id]="'delCol_'+column.columnName" [name]="'delOp_'+column.columnName" - (change)="emptyFillTextInput(column.columnName)"></label> - </div> - </div> - - <div class="flex-shrink-1 mx-3"> - <div class="input-group"> - <label class="form-control" [for]="'delRows_'+column.columnName">Izbriši + (change)="emptyFillTextInput(column.columnName)"> + </label> + </div> + <label class="form-control text-secondary" role="alert" *ngIf="column.columnName == this.experiment.outputColumn"> + (Izlazna kolona) + </label> + </div> + + <div class="flex-shrink-1 mx-3"> + <div class="input-group"> + <label class="form-control" [for]="'delRows_'+column.columnName">Izbriši redove <input type="radio" [id]="'delRows_'+column.columnName" [name]="'delOp_'+column.columnName" checked (change)="emptyFillTextInput(column.columnName)"></label> + </div> + </div> </div> </div> </div> @@ -165,96 +153,109 @@ </div> </div> </div> - </div> - - <div id="randomOptions" class="mt-5"> - <div class="p-2 m-2"> - <label for="type" class="form-check-label">Želite li da redosled podataka bude nasumičan?</label> - <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="experiment.randomOrder" - type="checkbox" value="" checked> - </div> - <div class="border m-3"> - <div class="row p-2 m-2"> - <div class="col-4"> - <label for="splitYesNo" class="form-check-label"> + + <div id="randomOptions" class="mt-5"> + <div class="p-2 m-2"> + <label for="type" class="form-check-label">Želite li da redosled podataka bude nasumičan?</label> + <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="experiment.randomOrder" type="checkbox" value="" checked> + </div> + <div class="border m-3"> + <div class="row p-2 m-2"> + <div class="col-4"> + <label for="splitYesNo" class="form-check-label"> <h3>Podela test skupa: <input id="splitYesNo" class="form-check-input" type="checkbox" [checked]="experiment.randomTestSet" (change)="experiment.randomTestSet = !experiment.randomTestSet"> </h3> </label> + </div> + <div class="col-8"> + trening + <mat-slider style="width: 85%;" min="10" max="90" step="10" value="10" name="randomTestSetDistribution" thumbLabel [disabled]="!experiment.randomTestSet" [(ngModel)]="tempTestSetDistribution"> + </mat-slider> + test + </div> </div> - <div class="col-8"> - trening - <mat-slider style="width: 85%;" min="10" max="90" step="10" value="10" - name="randomTestSetDistribution" thumbLabel [disabled]="!experiment.randomTestSet" - [(ngModel)]="tempTestSetDistribution"> - </mat-slider> - test - </div> - </div> - - <div class="row p-2 m-2"> - <label for="percentage" class="form-label">Procenat podataka koji se uzima za trening + + <div class="row p-2 m-2"> + <label for="percentage" class="form-label">Procenat podataka koji se uzima za trening skup:</label> - <input id="percentage" type="number" class="form-control mx-3" style=" max-width: 15%" min="10" max="90" step="10" value="90" - [(ngModel)]="tempTestSetDistribution" [disabled]="!experiment.randomTestSet"> + <input id="percentage" type="number" class="form-control mx-3" style=" max-width: 15%" min="10" max="90" step="10" value="90" [(ngModel)]="tempTestSetDistribution" [disabled]="!experiment.randomTestSet"> + </div> </div> </div> - </div> - - <div id="encodingForColumns" class="row px-3 my-5"> - <div class="col-1"> - <label for="encoding" class="col-form-label">Enkoding: </label> - </div> - <div class="col-2"> - <select id=encodingOptions class="form-control" name="encoding" [(ngModel)]="experiment.encoding"> - <option *ngFor="let option of Object.keys(Encoding); let optionName of Object.values(Encoding)" - [value]="option"> - {{ optionName }} - </option> - </select> + + <div id="encodingsForColumns" class="px-3 my-5"> + <label for="encoding" class="col-form-label mb-2">Enkoding za izabrane kolone:</label> + + <div class="row d-flex flex-row justify-content-between align-items-center"> + <div class="col-5" *ngFor="let item of [].constructor(selectedColumnsInfoArray.length); let i = index"> + <div class="input-group mb-2"> + <div class="input-group-prepend"> + <span class="input-group-text">{{selectedColumnsInfoArray[i].columnName}}</span> + </div> + <select [id]="'encodingOption_'+i" class="form-select" [(ngModel)]="experiment.encodings[i].encoding"> + <option + *ngFor="let option of Object.keys(Encoding); let optionName of Object.values(Encoding)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + </div> + </div> </div> + </div> - - </div> - </div> + </div> - <div class="carousel-item mt-2"> - <h2 class="mb-4">3. Dodaj eskperiment</h2> + <div class="carousel-item mt-2"> + <h2 class="mb-4">3. Dodaj eksperiment</h2> - <div class="row"> - <div class="col"></div> - <div class="col-8"> - <label for="name" class="col-form-label">Naziv eksperimenta:</label> - <input type="text" class="form-control mb-3" name="name" placeholder="Naziv..." [(ngModel)]="experiment.name"> - - <label for="desc" class="col-sm-2 col-form-label">Opis:</label> - <div> - <textarea class="form-control" name="desc" rows="3" [(ngModel)]="experiment.description"></textarea> - </div> - <div class="form-group row mt-5 mb-3"> - <div class="col"></div> - <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" (click)="saveExperiment();">Sačuvaj + <div class="row"> + <div class="col"></div> + <div class="col-8"> + <label for="name" class="col-form-label">Naziv eksperimenta:</label> + <input type="text" class="form-control mb-3" name="name" placeholder="Naziv..." [(ngModel)]="experiment.name"> + + <label for="desc" class="col-sm-2 col-form-label">Opis:</label> + <div> + <textarea class="form-control" name="desc" rows="3" [(ngModel)]="experiment.description"></textarea> + </div> + <label for="desc" class="col-sm-2 col-form-label mt-3">Tip problema:</label> + <div class="col-4"> + <select id="typeOptions" class="form-select" name="type" [(ngModel)]="experiment.type"> + <option + *ngFor="let option of Object.keys(ProblemType); let optionName of Object.values(ProblemType)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + <div class="form-group row mt-5 mb-3"> + <div class="col"></div> + <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" (click)="saveExperiment();">Sačuvaj eksperiment</button> - <div class="col"></div> + <div class="col"></div> + </div> </div> + <div class="col"></div> </div> - <div class="col"></div> </div> + </div> - </div> - <div class="m-3 d-flex flex-row justify-content-between align-items-center" style=" margin-left: auto;"> - <button mat-fab color="primary" data-bs-target="#carouselExampleControls" data-bs-slide="prev"> - <mat-icon>arrow_backward</mat-icon> - </button> - <button mat-fab color="primary" data-bs-target="#carouselExampleControls" data-bs-slide="next"> - <mat-icon>arrow_forward</mat-icon> - </button> - </div> + <div class="m-3 d-flex flex-row align-items-center" style=" margin-left: auto;"> + <button class="me-auto" *ngIf="carouselIndex != 0" mat-fab color="primary" data-bs-target="#carouselExampleControls" data-bs-slide="prev" (click)="updateCarouselIndex(carouselIndex - 1)"> + <mat-icon>arrow_backward</mat-icon> + </button> + <button class="ms-auto" *ngIf="carouselIndex != 2" mat-fab color="primary" data-bs-target="#carouselExampleControls" data-bs-slide="next" (click)="updateCarouselIndex(carouselIndex + 1)"> + <mat-icon>arrow_forward</mat-icon> + </button> + </div> </div> </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/experiment/experiment.component.ts b/frontend/src/app/experiment/experiment.component.ts index b5e1d1f4..64adce3f 100644 --- a/frontend/src/app/experiment/experiment.component.ts +++ b/frontend/src/app/experiment/experiment.component.ts @@ -1,10 +1,14 @@ import { Component, OnInit } from '@angular/core'; import Experiment, { NullValReplacer, NullValueOptions, ReplaceWith, Encoding } from '../_data/Experiment'; -import Model from '../_data/Model'; +import Model,{ProblemType} from '../_data/Model'; import Dataset, { ColumnInfo } from '../_data/Dataset'; import { ModelsService } from '../_services/models.service'; import Shared from '../Shared'; import { ExperimentsService } from '../_services/experiments.service'; +import { ColumnEncoding } from '../_data/Experiment'; +import { Router } from '@angular/router'; +import { TrainingComponent } from '../training/training.component'; +import { NEVER, retryWhen } from 'rxjs'; @Component({ selector: 'app-experiment', @@ -21,14 +25,16 @@ export class ExperimentComponent implements OnInit { NullValueOptions = NullValueOptions; ReplaceWith = ReplaceWith; Encoding = Encoding; + ColumnEncoding = ColumnEncoding; Object = Object; - + ProblemType=ProblemType; selectedColumnsInfoArray: ColumnInfo[] = []; selectedNotNullColumnsArray: string[] = []; tempTestSetDistribution = 90; + carouselIndex: number = 0; - constructor(private modelsService: ModelsService, private experimentsService: ExperimentsService) { + constructor(private experimentsService: ExperimentsService, private router: Router) { } ngOnInit(): void { @@ -38,6 +44,16 @@ export class ExperimentComponent implements OnInit { this.selectedDataset = dataset; this.selectedColumnsInfoArray = this.selectedDataset.columnInfo; this.selectedNotNullColumnsArray = []; + this.experiment.outputColumn = this.selectedDataset.columnInfo[this.selectedDataset.columnInfo.length - 1].columnName; + + this.resetColumnEncodings(); + } + + resetColumnEncodings() { + this.experiment.encodings = []; + for (let i = 0; i < this.selectedColumnsInfoArray.length; i++) { + this.experiment.encodings.push(new ColumnEncoding(this.selectedColumnsInfoArray[i].columnName, Encoding.Label)); + } } getInputById(id: string): HTMLInputElement { @@ -153,7 +169,7 @@ export class ExperimentComponent implements OnInit { } saveExperiment() { - if (this.selectedDataset == undefined) { + if (this.selectedDataset == undefined) { Shared.openDialog("Greška", "Izvor podataka nije izabran!"); return; } @@ -184,10 +200,9 @@ export class ExperimentComponent implements OnInit { this.experimentsService.addExperiment(this.experiment).subscribe((response) => { this.experiment = response; - this.selectedColumnsInfoArray = []; - this.selectedNotNullColumnsArray = []; - - Shared.openDialog("Obaveštenje", "Eksperiment je uspešno kreiran."); + Shared.openYesNoDialog("Obaveštenje", "Eksperiment je uspešno kreiran. Da li želite da pređete na treniranje modela?", () => { + this.router.navigate(['/training', this.experiment._id]); + }); }, (error) => { if (error.error == "Experiment with this name exists") { Shared.openDialog("Greška", "Eksperiment sa unetim nazivom već postoji u Vašoj kolekciji. Unesite neki drugi naziv."); @@ -205,4 +220,23 @@ export class ExperimentComponent implements OnInit { } return counter; } + + updateCarouselIndex(newIndex: number) { + if (newIndex > 2) + newIndex = 2; + else if (newIndex < 0) + newIndex = 0; + + if (this.carouselIndex == 0 && (newIndex == 1 || newIndex == 2)) + this.checkRequiredData(); + this.carouselIndex = newIndex; + } + + checkRequiredData() { + if (this.selectedDataset == undefined) { + (<HTMLAnchorElement>document.getElementById("firstStep")).click(); + Shared.openDialog("Pažnja", "Potrebno je da dodate ili izabere izvor podataka kako biste prešli na naredni korak (preprocesiranje)."); + return; + } + } } diff --git a/frontend/src/app/mixed-chart/mixed-chart.component.css b/frontend/src/app/mixed-chart/mixed-chart.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/mixed-chart/mixed-chart.component.css diff --git a/frontend/src/app/mixed-chart/mixed-chart.component.html b/frontend/src/app/mixed-chart/mixed-chart.component.html new file mode 100644 index 00000000..806ea9e8 --- /dev/null +++ b/frontend/src/app/mixed-chart/mixed-chart.component.html @@ -0,0 +1,2 @@ +<canvas #mixedchart width="800" height="450"></canvas> +<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script> diff --git a/frontend/src/app/mixed-chart/mixed-chart.component.spec.ts b/frontend/src/app/mixed-chart/mixed-chart.component.spec.ts new file mode 100644 index 00000000..361cd047 --- /dev/null +++ b/frontend/src/app/mixed-chart/mixed-chart.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { MixedChartComponent } from './mixed-chart.component'; + +describe('MixedChartComponent', () => { + let component: MixedChartComponent; + let fixture: ComponentFixture<MixedChartComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ MixedChartComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(MixedChartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/mixed-chart/mixed-chart.component.ts b/frontend/src/app/mixed-chart/mixed-chart.component.ts new file mode 100644 index 00000000..2524ee36 --- /dev/null +++ b/frontend/src/app/mixed-chart/mixed-chart.component.ts @@ -0,0 +1,56 @@ +import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import {Chart} from 'node_modules/chart.js'; + +@Component({ + selector: 'app-mixed-chart', + templateUrl: './mixed-chart.component.html', + styleUrls: ['./mixed-chart.component.css'] +}) +export class MixedChartComponent implements AfterViewInit { + + @ViewChild('mixedchart') chartRef!: ElementRef; + constructor() { } + + ngAfterViewInit(): void { + const myChart = new Chart(this.chartRef.nativeElement, { + type: 'bar', + data: { + labels: ["1900", "1950", "1999", "2050"], + datasets: [{ + label: "Europe", + type: "line", + borderColor: "#8e5ea2", + data: [408,547,675,734], + fill: false + }, { + label: "Africa", + type: "line", + borderColor: "#3e95cd", + data: [133,221,783,2478], + fill: false + }, { + label: "Europe", + type: "bar", + backgroundColor: "rgba(0,0,0,0.2)", + data: [408,547,675,734], + }, { + label: "Africa", + type: "bar", + backgroundColor: "rgba(0,0,0,0.2)", + //backgroundColorHover: "#3e95cd", + data: [133,221,783,2478] + } + ] + }, + /*options: { + title: { + display: true, + text: 'Population growth (millions): Europe & Africa' + }, + legend: { display: false } + }*/ + + }); + } + +} diff --git a/frontend/src/app/point-linechart/point-linechart.component.css b/frontend/src/app/point-linechart/point-linechart.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/point-linechart/point-linechart.component.css diff --git a/frontend/src/app/point-linechart/point-linechart.component.html b/frontend/src/app/point-linechart/point-linechart.component.html new file mode 100644 index 00000000..f9f9a24a --- /dev/null +++ b/frontend/src/app/point-linechart/point-linechart.component.html @@ -0,0 +1,2 @@ +<canvas #linechart width="800" height="450">Point line chart:</canvas> +<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.min.js"></script> diff --git a/frontend/src/app/point-linechart/point-linechart.component.spec.ts b/frontend/src/app/point-linechart/point-linechart.component.spec.ts new file mode 100644 index 00000000..fe08fe7c --- /dev/null +++ b/frontend/src/app/point-linechart/point-linechart.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PointLinechartComponent } from './point-linechart.component'; + +describe('PointLinechartComponent', () => { + let component: PointLinechartComponent; + let fixture: ComponentFixture<PointLinechartComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PointLinechartComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PointLinechartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/point-linechart/point-linechart.component.ts b/frontend/src/app/point-linechart/point-linechart.component.ts new file mode 100644 index 00000000..3497a20c --- /dev/null +++ b/frontend/src/app/point-linechart/point-linechart.component.ts @@ -0,0 +1,57 @@ +import { AfterViewInit, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import {Chart} from 'node_modules/chart.js'; + + +@Component({ + selector: 'app-point-linechart', + templateUrl: './point-linechart.component.html', + styleUrls: ['./point-linechart.component.css'] +}) +export class PointLinechartComponent implements AfterViewInit { + + @ViewChild('linechart') chartRef!: ElementRef; + constructor() { } + ngAfterViewInit(): void { + const myChart = new Chart(this.chartRef.nativeElement, { + type: 'line', + data: { + labels: [1500,1600,1700,1750,1800,1850,1900,1950,1999,2050], + datasets: [{ + data: [86,114,106,106,107,111,133,221,783,2478], + label: "Africa", + borderColor: "#3e95cd", + fill: false + }, { + data: [282,350,411,502,635,809,947,1402,3700,5267], + label: "Asia", + borderColor: "#8e5ea2", + fill: false + }, { + data: [168,170,178,190,203,276,408,547,675,734], + label: "Europe", + borderColor: "#3cba9f", + fill: false + }, { + data: [40,20,10,16,24,38,74,167,508,784], + label: "Latin America", + borderColor: "#e8c3b9", + fill: false + }, { + data: [6,3,2,2,7,26,82,172,312,433], + label: "North America", + borderColor: "#c45850", + fill: false + } + ] + }, + /*options: { + title: { + display: true, + text: 'World population per region (in millions)' + } + }*/ + + }); + + } +}
\ No newline at end of file diff --git a/frontend/src/app/training/training.component.html b/frontend/src/app/training/training.component.html index 0ce4cc89..deac88cc 100644 --- a/frontend/src/app/training/training.component.html +++ b/frontend/src/app/training/training.component.html @@ -2,45 +2,40 @@ <h1>Trenirajte veštačku neuronsku mrežu</h1> </div> <div id="wrapper" class="mb-4"> - <div id="container" class="container p-5 row" style="background-color: white; min-height: 100%;"> - <div class="col"></div> + <div id="container" class="container p-5" style="background-color: white; min-height: 100%;"> + <h2>1. Izaberite eksperiment iz kolekcije</h2> + <div class="px-5 mt-5 mb-3"> + <input type="text" class="form-control" placeholder="Pretraga" [(ngModel)]="term"> + </div> + <div class="overflow-auto px-5" style="max-height: 500px;"> + <ul class="list-group"> + <li class="list-group-item p-3" *ngFor="let experiment of myExperiments|filter:term" [ngClass]="{'selectedExperimentClass': this.selectedExperiment == experiment}"> + <app-item-experiment [experiment]="experiment" (click)="selectThisExperiment(experiment);"></app-item-experiment> + </li> + </ul> + </div> - <div class="col-10"> + <h2 class="mt-5 mb-2">2. Izaberite model</h2> + <app-model-load *ngIf="selectedExperiment" (selectedModelChangeEvent)="selectModel($event)" [forExperiment]="selectedExperiment"></app-model-load> + <h3 *ngIf="!selectedExperiment">Morate prvo izabrati eksperiment.</h3> - <h2>1. Izaberite eksperiment iz kolekcije</h2> - <div class="px-5 mt-5 mb-3"> - <input type="text" class="form-control" placeholder="Pretraga" - [(ngModel)]="term"> - </div> - <div class="overflow-auto px-5" style="max-height: 500px;"> - <ul class="list-group"> - <li class="list-group-item p-3" *ngFor="let experiment of myExperiments|filter:term" - [ngClass]="{'selectedExperimentClass': this.selectedExperiment == experiment}"> - <app-item-experiment [experiment]="experiment" - (click)="selectThisExperiment(experiment);"></app-item-experiment> - </li> - </ul> - </div> - - <h2 class="mt-5 mb-2">2. Izaberite model</h2> - <app-model-load (selectedModelChangeEvent)="selectModel($event)"></app-model-load> - - <h2 class="my-5">3. Treniranje modela</h2> + <h2 class="my-5">3. Treniranje modela</h2> - <div class="d-flex flex-row justify-content-center align-items-center my-3"> - <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" (click)="trainModel();">Treniraj + <div class="d-flex flex-row justify-content-center align-items-center my-3"> + <button #trainButton class="btn btn-lg col-4" style="background-color:#003459; color:white;" (click)="trainModel();">Treniraj model</button> - </div> - - <h2 class="mt-5">Rezultati treniranja</h2> - <div class="m-3" *ngIf="trainingResult"> - <h2 class="my-2">Rezultati treniranja:</h2> - <p> - {{trainingResult}} - </p> - </div> </div> - <div class="col"></div> + <h2 class="mt-5">Rezultati treniranja</h2> + <div class="m-3" *ngIf="trainingResult"> + <h2 class="my-2">Rezultati treniranja:</h2> + <app-metric-view *ngIf="trainingResult" [metrics]="trainingResult"></app-metric-view> + </div> + </div> + + <h2 class="mt-5">Rezultati treniranja</h2> + <div class="m-3" *ngIf="trainingResult"> + <h2 class="my-2">Rezultati treniranja:</h2> + <app-metric-view *ngIf="trainingResult" [metrics]="trainingResult"></app-metric-view> </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/training/training.component.ts b/frontend/src/app/training/training.component.ts index 027d2c22..a0bec8e2 100644 --- a/frontend/src/app/training/training.component.ts +++ b/frontend/src/app/training/training.component.ts @@ -1,17 +1,26 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; import Shared from '../Shared'; import Experiment from '../_data/Experiment'; -import Model from '../_data/Model'; -import { DatasetsService } from '../_services/datasets.service'; +import Model, { ProblemType } from '../_data/Model'; +import { MetricViewComponent } from '../_elements/metric-view/metric-view.component'; +import { ModelLoadComponent } from '../_elements/model-load/model-load.component'; +import { AuthService } from '../_services/auth.service'; import { ExperimentsService } from '../_services/experiments.service'; import { ModelsService } from '../_services/models.service'; +import { SignalRService } from '../_services/signal-r.service'; @Component({ selector: 'app-training', templateUrl: './training.component.html', styleUrls: ['./training.component.css'] }) -export class TrainingComponent{ +export class TrainingComponent implements OnInit { + + @ViewChild(ModelLoadComponent) modelLoadComponent?: ModelLoadComponent; + @ViewChild("trainButton") trainButtonRef!: ElementRef; + + @ViewChild(MetricViewComponent) metricViewComponent!: MetricViewComponent; myExperiments?: Experiment[]; selectedExperiment?: Experiment; @@ -19,16 +28,55 @@ export class TrainingComponent{ trainingResult: any; + history: any[] = []; + term: string = ""; - constructor(private modelsService: ModelsService, private datasetsService: DatasetsService, private experimentsService: ExperimentsService) { + constructor(private modelsService: ModelsService, private route: ActivatedRoute, private experimentsService: ExperimentsService, private authService: AuthService, private signalRService: SignalRService) { + } + + ngOnInit(): void { + this.route.queryParams.subscribe(params => { + let experimentId = this.route.snapshot.paramMap.get("id"); + + this.fetchExperiments(experimentId); + + this.authService.loggedInEvent.subscribe(_ => { + this.fetchExperiments(experimentId); + + this.signalRService.startConnection(); + }); + + console.log(this.signalRService.hubConnection); + if (this.signalRService.hubConnection) { + this.signalRService.hubConnection.on("NotifyEpoch", (mName: string, mId: string, stat: string, totalEpochs: number, currentEpoch: number) => { + console.log(this.selectedModel?._id, mId); + if (this.selectedModel?._id == mId) { + stat = stat.replace(/'/g, '"'); + this.trainingResult = JSON.parse(stat); + //console.log('JSON', this.trainingResult); + this.history.push(this.trainingResult); + this.metricViewComponent.update(this.history); + } + }); + } + }); + } + + fetchExperiments(andSelectWithId: string | null = '') { this.experimentsService.getMyExperiments().subscribe((experiments) => { - this.myExperiments = experiments; + this.myExperiments = experiments.reverse(); + + this.selectedExperiment = this.myExperiments.filter(x => x._id == andSelectWithId)[0]; + if (this.modelLoadComponent) + this.modelLoadComponent.newModel.type = this.selectedExperiment.type; }); } selectThisExperiment(experiment: Experiment) { this.selectedExperiment = experiment; + if (this.modelLoadComponent) + this.modelLoadComponent.newModel.type = this.selectedExperiment.type; } selectModel(model: Model) { @@ -46,9 +94,11 @@ export class TrainingComponent{ Shared.openDialog("Greška", "Molimo Vas da izaberete model."); return; } + this.trainButtonRef.nativeElement.disabled = true; this.modelsService.trainModel(this.selectedModel._id, this.selectedExperiment._id).subscribe((response: any) => { //console.log('Train model complete!', response); - Shared.openDialog("Obaveštenje", "Treniranje modela je uspešno završeno!"); + this.trainButtonRef.nativeElement.disabled = false; + Shared.openDialog("Obaveštenje", "Treniranje modela je počelo!"); this.trainingResult = response; }); } |