diff options
25 files changed, 1238 insertions, 85 deletions
diff --git a/backend/api/api/Controllers/AuthController.cs b/backend/api/api/Controllers/AuthController.cs index 901454e1..f70146ed 100644 --- a/backend/api/api/Controllers/AuthController.cs +++ b/backend/api/api/Controllers/AuthController.cs @@ -4,6 +4,8 @@ using Microsoft.AspNetCore.Mvc; using api.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.Net.Http.Headers; +using System.Net.Http.Headers; +using api.Models; namespace api.Controllers { @@ -12,16 +14,20 @@ namespace api.Controllers public class AuthController : ControllerBase { private IAuthService _auth; - public AuthController(IAuthService auth) + private IJwtToken _jwtToken; + public AuthController(IAuthService auth, IJwtToken Token) { _auth = auth; + _jwtToken = Token; } [HttpPost("register")] public async Task<ActionResult<string>> Register(RegisterRequest user) { - - return Ok(_auth.Register(user)); + string id=getUserId(); + if (id == null) + return BadRequest(); + return Ok(_auth.Register(user,id)); } [HttpPost("login")] @@ -45,7 +51,7 @@ namespace api.Controllers } [HttpPost("renewJwt")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public async Task<ActionResult<string>> RenewJwt() { var authorization = Request.Headers[HeaderNames.Authorization]; @@ -57,6 +63,24 @@ namespace api.Controllers } + public string getUserId() + { + 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 null; + + return uploaderId; + } + } diff --git a/backend/api/api/Controllers/DatasetController.cs b/backend/api/api/Controllers/DatasetController.cs index 1873d9ec..a6ebe8ac 100644 --- a/backend/api/api/Controllers/DatasetController.cs +++ b/backend/api/api/Controllers/DatasetController.cs @@ -67,7 +67,7 @@ namespace api.Controllers //desc - opadajuce 0 //ako se posalje 0 kao latest onda ce da izlista sve u nekom poretku [HttpGet("datesort/{ascdsc}/{latest}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult<List<Dataset>> SortDatasets(bool ascdsc, int latest) { string userId = getUserId(); @@ -98,7 +98,7 @@ namespace api.Controllers //SEARCH za datasets (public ili private sa ovim imenom ) // GET api/<DatasetController>/search/{name} [HttpGet("search/{name}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult<List<Dataset>> Search(string name) { return _datasetService.SearchDatasets(name); @@ -108,7 +108,7 @@ namespace api.Controllers // GET api/<DatasetController>/{name} //get odredjeni dataset [HttpGet("{name}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult<Dataset> Get(string name) { string userId = getUserId(); @@ -138,7 +138,7 @@ namespace api.Controllers var existingDataset = _datasetService.GetOneDatasetN(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); @@ -152,7 +152,7 @@ namespace api.Controllers // PUT api/<DatasetController>/{name} [HttpPut("{id}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult Put(string id, [FromBody] Dataset dataset) { string uploaderId = getUserId(); @@ -175,7 +175,7 @@ namespace api.Controllers // DELETE api/<DatasetController>/name [HttpDelete("{id}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult Delete(string id) { string uploaderId = getUserId(); diff --git a/backend/api/api/Controllers/ExperimentController.cs b/backend/api/api/Controllers/ExperimentController.cs index 6f1bbd42..08354615 100644 --- a/backend/api/api/Controllers/ExperimentController.cs +++ b/backend/api/api/Controllers/ExperimentController.cs @@ -92,7 +92,7 @@ namespace api.Controllers // PUT api/<ExperimentController>/{name} [HttpPut("{id}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult Put(string id, [FromBody] Experiment experiment) { string uploaderId = getUserId(); @@ -114,7 +114,7 @@ namespace api.Controllers // DELETE api/<ExperimentController>/name [HttpDelete("{id}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult Delete(string id) { string uploaderId = getUserId(); diff --git a/backend/api/api/Controllers/ModelController.cs b/backend/api/api/Controllers/ModelController.cs index 2916fa98..a0e51e1f 100644 --- a/backend/api/api/Controllers/ModelController.cs +++ b/backend/api/api/Controllers/ModelController.cs @@ -100,7 +100,7 @@ namespace api.Controllers // GET: api/<ModelController>/mymodels [HttpGet("mymodels")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult<List<Model>> Get() { string uploaderId = getUserId(); @@ -113,7 +113,7 @@ namespace api.Controllers // GET: api/<ModelController>/mymodels [HttpGet("mymodelsbytype/{problemtype}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult<List<Model>> GetMyModelsByType(string problemType) { string uploaderId = getUserId(); @@ -132,7 +132,7 @@ namespace api.Controllers // vraca svoj model prema nekom imenu // GET api/<ModelController>/{name} [HttpGet("{name}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult<Model> Get(string name) { string userId = getUserId(); @@ -156,7 +156,7 @@ namespace api.Controllers //odraditi to i u Datasetove i Predictore // GET: api/<ModelController>/getlatestmodels/{number} [HttpGet("getlatestmodels/{latest}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult<List<Model>> GetLatestModels(int latest) { string userId = getUserId(); @@ -213,7 +213,7 @@ namespace api.Controllers // PUT api/<ModelController>/{name} [HttpPut("{name}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult Put(string name, [FromBody] Model model) { string userId = getUserId(); @@ -233,7 +233,7 @@ namespace api.Controllers // DELETE api/<ModelController>/name [HttpDelete("{name}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult Delete(string name) { string userId = getUserId(); diff --git a/backend/api/api/Controllers/PredictorController.cs b/backend/api/api/Controllers/PredictorController.cs index dd5aa5fd..3646187e 100644 --- a/backend/api/api/Controllers/PredictorController.cs +++ b/backend/api/api/Controllers/PredictorController.cs @@ -52,7 +52,7 @@ namespace api.Controllers // GET: api/<PredictorController>/mypredictors [HttpGet("mypredictors")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult<List<Predictor>> Get() { string userId = getUserId(); @@ -105,7 +105,7 @@ namespace api.Controllers // GET api/<PredictorController>/{name} [HttpGet("{name}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult<Predictor> Get(string id) { string userId = getUserId(); @@ -128,7 +128,7 @@ namespace api.Controllers //desc - opadajuce 0 //ako se posalje 0 kao latest onda ce da izlista sve u nekom poretku [HttpGet("datesort/{ascdsc}/{latest}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult<List<Predictor>> SortPredictors(bool ascdsc, int latest) { string userId = getUserId(); @@ -193,7 +193,7 @@ namespace api.Controllers // PUT api/<PredictorController>/{name} [HttpPut("{name}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult Put(string id, [FromBody] Predictor predictor) { string userId = getUserId(); @@ -214,7 +214,7 @@ namespace api.Controllers // DELETE api/<PredictorController>/name [HttpDelete("{id}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult Delete(string id) { string userId = getUserId(); diff --git a/backend/api/api/Interfaces/IAuthService.cs b/backend/api/api/Interfaces/IAuthService.cs index 9a109208..570ce0a4 100644 --- a/backend/api/api/Interfaces/IAuthService.cs +++ b/backend/api/api/Interfaces/IAuthService.cs @@ -5,7 +5,7 @@ namespace api.Services public interface IAuthService { string Login(AuthRequest user); - string Register(RegisterRequest user); + string Register(RegisterRequest user, string id); string RenewToken(string token); public string GuestToken(); } diff --git a/backend/api/api/Interfaces/IJwtToken.cs b/backend/api/api/Interfaces/IJwtToken.cs index 2afb6683..5c54e4e3 100644 --- a/backend/api/api/Interfaces/IJwtToken.cs +++ b/backend/api/api/Interfaces/IJwtToken.cs @@ -4,8 +4,8 @@ namespace api.Models { public interface IJwtToken { - string GenGuestToken(); - string GenToken(AuthRequest user); + string GenGuestToken(string id); + string GenToken(User user); string RenewToken(string existingToken); string TokenToUsername(string token); public string TokenToId(string token); diff --git a/backend/api/api/Models/User.cs b/backend/api/api/Models/User.cs index 1ae8e437..bea467fa 100644 --- a/backend/api/api/Models/User.cs +++ b/backend/api/api/Models/User.cs @@ -25,6 +25,8 @@ namespace api.Models public string LastName { get; set; } public string photoId { get; set; } - + public bool isPermament { get; set; } + public DateTime dateCreated { get; set; } + } } diff --git a/backend/api/api/Services/AuthService.cs b/backend/api/api/Services/AuthService.cs index c7161dee..672511b3 100644 --- a/backend/api/api/Services/AuthService.cs +++ b/backend/api/api/Services/AuthService.cs @@ -25,10 +25,10 @@ namespace api.Services return "Username doesn't exist"; if (!PasswordCrypt.checkPassword(user.Password, u.Password)) return "Wrong password"; - return _jwt.GenToken(user); + return _jwt.GenToken(u); } - public string Register(RegisterRequest user) + public string Register(RegisterRequest user,string id) { User u = new User(); u.Username = user.username; @@ -37,12 +37,15 @@ namespace api.Services u.FirstName = user.firstName; u.LastName = user.lastName; u.photoId = "1"; + u.isPermament = true; + u._id = id; + u.dateCreated= DateTime.Now.ToUniversalTime(); if (_users.Find(user => user.Username == u.Username).FirstOrDefault() != null) return "Username Already Exists"; if (_users.Find(user => user.Email == u.Email).FirstOrDefault() != null) return "Email Already Exists"; - _users.InsertOne(u); + _users.ReplaceOne(x=>x._id==u._id,u); return "User added"; } @@ -60,7 +63,12 @@ namespace api.Services public string GuestToken() { - return _jwt.GenGuestToken(); + User u = new User(); + u._id = ""; + u.dateCreated = DateTime.Now.ToUniversalTime(); + _users.InsertOne(u); + return _jwt.GenGuestToken(u._id); + } diff --git a/backend/api/api/Services/JwtToken.cs b/backend/api/api/Services/JwtToken.cs index 06b3a666..20b0bc73 100644 --- a/backend/api/api/Services/JwtToken.cs +++ b/backend/api/api/Services/JwtToken.cs @@ -19,16 +19,17 @@ namespace api.Models } - public string GenToken(AuthRequest user) + public string GenToken(User user) { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(_configuration.GetSection("AppSettings:JwtToken").Value); - var fullUser = _userService.GetUserByUsername(user.UserName); + string role=(user.isPermament)?"User":"Guest"; + string name = (user.isPermament) ? user.Username : ""; var tokenDescriptor = new SecurityTokenDescriptor { - Subject = new ClaimsIdentity(new[] { new Claim("name", fullUser.Username), - new Claim("role", "User"), - new Claim("id",fullUser._id)}), + Subject = new ClaimsIdentity(new[] { new Claim("name", name), + new Claim("role", role), + new Claim("id",user._id)}), Expires = DateTime.UtcNow.AddMinutes(20), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; @@ -39,13 +40,12 @@ namespace api.Models public string RenewToken(string existingToken) { - var userName = TokenToUsername(existingToken); - if (userName == null) + var id = TokenToId(existingToken); + if (id == null) return null; - var authUser = new AuthRequest(); - authUser.UserName = userName; + var user = _userService.GetUserById(id); - return GenToken(authUser); + return GenToken(user); } @@ -100,15 +100,16 @@ namespace api.Models } - public string GenGuestToken() + public string GenGuestToken(string id) { + var user=_userService.GetUserById(id); var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes(_configuration.GetSection("AppSettings:JwtToken").Value); var tokenDescriptor = new SecurityTokenDescriptor { Subject = new ClaimsIdentity(new[] { new Claim("name",""), new Claim("role", "Guest"), - new Claim("id","")}), + new Claim("id",user._id)}), Expires = DateTime.UtcNow.AddMinutes(20), SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; diff --git a/backend/api/api/Services/TempRemovalService.cs b/backend/api/api/Services/TempRemovalService.cs index 302ca974..9e6b7f96 100644 --- a/backend/api/api/Services/TempRemovalService.cs +++ b/backend/api/api/Services/TempRemovalService.cs @@ -10,6 +10,8 @@ namespace api.Services private readonly IMongoCollection<Model> _model; private readonly IMongoCollection<Dataset> _dataset; private readonly IMongoCollection<Experiment> _experiment; + private readonly IMongoCollection<User> _user; + private readonly IMongoCollection<Predictor> _predictor; public TempRemovalService(IUserStoreDatabaseSettings settings, IMongoClient mongoClient) { @@ -18,43 +20,42 @@ namespace api.Services _model= database.GetCollection<Model>(settings.ModelCollectionName); _dataset = database.GetCollection<Dataset>(settings.DatasetCollectionName); _experiment= database.GetCollection<Experiment>(settings.ExperimentCollectionName); + _user = database.GetCollection<User>(settings.CollectionName); + _predictor = database.GetCollection<Predictor>(settings.PredictorCollectionName); + } - public void DeleteTemps() + public void DeleteTemps() { - List<FileModel> files = _file.Find(file => file.uploaderId == "").ToList(); - foreach (var file in files) + List<User> tempUsers=_user.Find(u=>u.isPermament==false).ToList(); + foreach (User user in tempUsers) { - if ((DateTime.Now.ToUniversalTime() - file.date).TotalDays >= 1) + if ((DateTime.Now.ToUniversalTime() - user.dateCreated).TotalDays < 1) + continue; + List<Predictor> tempPredictors=_predictor.Find(p=>p.uploaderId==user._id).ToList(); + List<Model> tempModels=_model.Find(m=>m.uploaderId==user._id).ToList(); + List<Experiment> tempExperiment = _experiment.Find(e => e.uploaderId == user._id).ToList(); + List<Dataset> tempDatasets = _dataset.Find(d => d.uploaderId == user._id).ToList(); + List<FileModel> tempFiles = _file.Find(f => f.uploaderId == user._id).ToList(); + + + foreach (Predictor predictor in tempPredictors) + DeletePredictor(predictor._id); + foreach(Model model in tempModels) + DeleteModel(model._id); + foreach(Experiment experiment in tempExperiment) + DeleteExperiment(experiment._id); + foreach(Dataset dataset in tempDatasets) + DeleteDataset(dataset._id); + foreach(FileModel file in tempFiles) { DeleteFile(file._id); - List<Dataset> datasets = _dataset.Find(dataset => dataset.fileId == file._id && dataset.uploaderId=="").ToList(); - foreach(var dataset in datasets) - { - DeleteDataset(dataset._id); - List<Experiment> experiments = _experiment.Find(experiment=>experiment.datasetId== dataset._id && experiment.uploaderId=="").ToList(); - foreach(var experiment in experiments) - { - DeleteExperiment(experiment._id); - foreach(var modelId in experiment.ModelIds) - { - var delModel=_model.Find(model=> modelId== model._id && model.uploaderId=="").FirstOrDefault(); - if(delModel!= null) - DeleteModel(delModel._id); - } - } - } - if (File.Exists(file.path)) + if(File.Exists(file.path)) File.Delete(file.path); } - } - //Brisanje modela ukoliko gost koristi vec postojeci dataset - List<Model> models1= _model.Find(model =>model.uploaderId == "").ToList(); - foreach(var model in models1) - { - if ((DateTime.Now.ToUniversalTime() - model.dateCreated.ToUniversalTime()).TotalDays >= 1) - { - DeleteModel(model._id); - } + DeleteUser(user._id); + + + } @@ -79,6 +80,14 @@ namespace api.Services { _experiment.DeleteOne(experiment => experiment._id == id); } + public void DeletePredictor(string id) + { + _predictor.DeleteOne(predictor=> predictor._id == id); + } + public void DeleteUser(string id) + { + _user.DeleteOne(user=>user._id == id); + } } diff --git a/frontend/src/app/_data/Experiment.ts b/frontend/src/app/_data/Experiment.ts index 828431cc..cff77535 100644 --- a/frontend/src/app/_data/Experiment.ts +++ b/frontend/src/app/_data/Experiment.ts @@ -1,5 +1,4 @@ import { ProblemType } from "./Model"; - export default class Experiment { _id: string = ''; uploaderId: string = ''; diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.ts b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts new file mode 100644 index 00000000..73dbf2d2 --- /dev/null +++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts @@ -0,0 +1,100 @@ +import { Component, OnInit, ViewChild, ViewChildren } from '@angular/core'; +import { AddNewDatasetComponent } from '../add-new-dataset/add-new-dataset.component'; +import { ModelsService } from 'src/app/_services/models.service'; +import shared from 'src/app/Shared'; +import Dataset from 'src/app/_data/Dataset'; +import { DatatableComponent, TableData } from 'src/app/_elements/datatable/datatable.component'; +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'; + +@Component({ + selector: 'app-dataset-load', + templateUrl: './dataset-load.component.html', + styleUrls: ['./dataset-load.component.css'] +}) +export class DatasetLoadComponent implements OnInit { + + @Output() selectedDatasetChangeEvent = new EventEmitter<Dataset>(); + + @ViewChild(AddNewDatasetComponent) addNewDatasetComponent!: AddNewDatasetComponent; + @ViewChild(AddNewDatasetComponent) datatable!: DatatableComponent; + + datasetLoaded: boolean = false; + selectedDatasetLoaded: boolean = false; + + showMyDatasets: boolean = true; + myDatasets?: Dataset[]; + existingDatasetSelected: boolean = false; + selectedDataset?: Dataset; + + tableData: TableData = new TableData(); + + term: string = ""; + + constructor(private models: ModelsService, private datasets: DatasetsService, private csv: CsvParseService, private signalRService: SignalRService) { + this.datasets.getMyDatasets().subscribe((datasets) => { + this.myDatasets = datasets; + }); + } + + viewMyDatasetsForm() { + this.showMyDatasets = true; + if (this.selectedDataset != undefined) + this.resetSelectedDataset(); + //this.resetCbsAndRbs(); //TREBA DA SE DESI + } + viewNewDatasetForm() { + this.showMyDatasets = false; + 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; + this.existingDatasetSelected = true; + this.tableData.hasHeader = this.selectedDataset.hasHeader; + + this.tableData.hasInput = true; + this.tableData.loaded = false; + + this.datasets.getDatasetFile(dataset.fileId).subscribe((file: string | undefined) => { + if (file) { + this.tableData.loaded = true; + this.tableData.numRows = this.selectedDataset!.rowCount; + this.tableData.numCols = this.selectedDataset!.columnInfo.length; + this.tableData.data = this.csv.csvToArray(file, (dataset.delimiter == "razmak") ? " " : (dataset.delimiter == "") ? "," : dataset.delimiter); + //this.resetCbsAndRbs(); //TREBA DA SE DESI + //this.refreshThreeNullValueRadioOptions(); //TREBA DA SE DESI + this.selectedDatasetLoaded = true; + + this.selectedDatasetChangeEvent.emit(this.selectedDataset); + } + }); + } + + resetSelectedDataset(): boolean { + this.selectedDatasetChangeEvent.emit(this.selectedDataset); + return true; + } + + ngOnInit(): void { + if (this.signalRService.hubConnection) { + this.signalRService.hubConnection.on("NotifyDataset", _ => { + this.refreshMyDatasets(); + }); + } else { + console.warn("Dataset-Load: No connection!"); + } + } +} diff --git a/frontend/src/app/_elements/model-load/model-load.component.html b/frontend/src/app/_elements/model-load/model-load.component.html new file mode 100644 index 00000000..dcb35c21 --- /dev/null +++ b/frontend/src/app/_elements/model-load/model-load.component.html @@ -0,0 +1,215 @@ +<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}"> + 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}"> + Dodajte novi model + </button> + </div> + + <div *ngIf="showMyModels" class="px-5 my-3"> + <input *ngIf="showMyModels" type="text" class="form-control" placeholder="Pretraga" [(ngModel)]="term"> + </div> + <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|filter:(forExperiment ? forExperiment.type : '')" + [ngClass]="{'selectedModelClass': this.selectedModel == model}"> + <app-item-model name="usersModel" [model]="model" (click)="selectThisModel(model);"> + </app-item-model> + </li> + </ul> + <div class="px-5 mt-5"> + <!--prikaz izabranog modela--> + </div> + </div> + </div> + + + <div *ngIf="!showMyModels"> + <div class="form-group row mt-3 mb-2 d-flex justify-content-center"> + + <div class="col-3"> + <label for="name" class="col-form-label">Naziv modela:</label> + <input type="text" class="form-control" name="name" placeholder="Naziv..." [(ngModel)]="newModel.name"> + </div> + <div class="col-5"> + <label for="desc" class="col-sm-2 col-form-label">Opis:</label> + <div> + <textarea class="form-control" name="desc" rows="3" [(ngModel)]="newModel.description"></textarea> + </div> + </div> + + </div> + <h2 class="mt-5 mb-4 mx-5">Parametri treniranja modela:</h2> + <div> + + <div class="row p-2"> + <div class="col-1"></div> + <div class="col-3"> + <label for="type" class="col-form-label">Tip problema: </label> + </div> + <div class="col-2"> + <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"> + {{ optionName }} + </option> + </select> + </div> + <div class="col-1"></div> + <div class="col-3"> + <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()"> + </div> + </div> + + <div class="row p-2"> + <div class="col-1"> + </div> + <div class="col-3"> + <label for="optimizer" class="col-form-label">Optimizacija: </label> + </div> + <div class="col-2"> + <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"> + {{ optionName }} + </option> + </select> + </div> + <div class="col-1"> + </div> + <div class="col-3"> + <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()"> + </div> + </div> + + <div class="row p-2"> + <div class="col-1"></div> + <div class="col-3"> + <label for="lossFunction" class="col-form-label">Funkcija troška: </label> + </div> + <div class="col-2"> + <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"> + {{ optionName }} + </option> + </select> + </div> + <div class="col-1"></div> + <div class="col-3"> + <label for="batchSize" class="col-form-label">Broj uzorka po iteraciji: </label> + </div> + <div class="col-1"> + + <input type="number" min="0" step="1" max="7" class="form-control" name="batchSizePower" [(ngModel)]="batchSizePower" (click)="updateBatchSize()" > + {{newModel.batchSize}} + + </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> + + <div class="m-5"> + <app-graph [model]="newModel" [inputCols]="1"></app-graph> + </div> + + <h3 class="mx-5 mt-4">Aktivacione funkcije:</h3> + + <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> + </div> + <div class="col-2 mt-2"> + <div *ngFor="let item of [].constructor(newModel.hiddenLayers); let i = index"> + <div class="input-group mb-2"> + <div class="input-group-prepend"> + <span class="input-group-text">#{{i+1}}</span> + </div> + <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"> + {{ optionName }} + </option> + </select> + </div> + </div> + </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> + </div> + <div class="col-2 mt-2"> + <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"> + {{ optionName }} + </option> + </select> + </div> + <div class="col"> + </div> + </div> + </div> + + <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"> + + <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 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 + model</button> + <div class="col"></div> + </div> + </div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/model-load/model-load.component.ts b/frontend/src/app/_elements/model-load/model-load.component.ts new file mode 100644 index 00000000..dbca3d17 --- /dev/null +++ b/frontend/src/app/_elements/model-load/model-load.component.ts @@ -0,0 +1,114 @@ +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 { ModelsService } from 'src/app/_services/models.service'; +import { GraphComponent } from '../graph/graph.component'; + + +@Component({ + selector: 'app-model-load', + templateUrl: './model-load.component.html', + styleUrls: ['./model-load.component.css'] +}) +export class ModelLoadComponent implements OnInit { + + @ViewChild(GraphComponent) graph!: GraphComponent; + @Input() forExperiment?:Experiment; + @Output() selectedModelChangeEvent = new EventEmitter<Model>(); + + newModel: Model = new Model(); + myModels?: Model[]; + selectedModel?: Model; + + ProblemType = ProblemType; + ActivationFunction = ActivationFunction; + metrics: any = Metrics; + LossFunction = LossFunction; + Optimizer = Optimizer; + Object = Object; + document = document; + shared = Shared; + + term: string = ""; + selectedProblemType: string = ''; + selectedMetrics = []; + lossFunction: any = LossFunction; + + showMyModels: boolean = true; + + constructor(private modelsService: ModelsService) { + this.modelsService.getMyModels().subscribe((models) => { + this.myModels = models; + }); + } + + ngOnInit(): void { + } + batchSizePower:number=1; + updateBatchSize() + { + this.newModel.batchSize=2**this.batchSizePower; + } + + updateGraph() { + this.graph.update(); + } + + getMetrics() { + this.newModel.metrics = []; + let cb = document.getElementsByName("cbmetrics"); + + for (let i = 0; i < cb.length; i++) { + let chb = <HTMLInputElement>cb[i]; + if (chb.checked == true) + this.newModel.metrics.push(chb.value); + } + } + + uploadModel() { + this.getMetrics(); + + 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 = + }, (error) => { + Shared.openDialog('Greška', 'Model sa unetim nazivom već postoji u Vašoj kolekciji. Promenite naziv modela i nastavite sa kreiranim datasetom.'); + }); + } + + filterOptions() { + switch (this.newModel.type) { + case 'regresioni': + this.lossFunction = LossFunctionRegression; + this.metrics = MetricsRegression; + break; + case 'binarni-klasifikacioni': + this.lossFunction = LossFunctionBinaryClassification; + this.metrics = MetricsBinaryClassification; + break; + case 'multi-klasifikacioni': + this.lossFunction = LossFunctionMultiClassification; + this.metrics = MetricsMultiClassification; + break; + default: + break; + } + } + + viewMyModelsForm() { + this.showMyModels = true; + } + viewNewModelForm() { + this.showMyModels = false; + } + + selectThisModel(model: Model) { + this.selectedModel = model; + this.selectedModelChangeEvent.emit(this.selectedModel); + } + +} diff --git a/frontend/src/app/_modals/login-modal/login-modal.component.ts b/frontend/src/app/_modals/login-modal/login-modal.component.ts index 062a0550..ccd78509 100644 --- a/frontend/src/app/_modals/login-modal/login-modal.component.ts +++ b/frontend/src/app/_modals/login-modal/login-modal.component.ts @@ -50,7 +50,9 @@ export class LoginModalComponent implements OnInit { this.userInfoService.getUserInfo().subscribe((response) => { shared.photoId = response.photoId; }); + location.reload(); } + }); } else { diff --git a/frontend/src/app/_modals/register-modal/register-modal.component.ts b/frontend/src/app/_modals/register-modal/register-modal.component.ts index 3726b2e0..9da5484d 100644 --- a/frontend/src/app/_modals/register-modal/register-modal.component.ts +++ b/frontend/src/app/_modals/register-modal/register-modal.component.ts @@ -162,6 +162,7 @@ export class RegisterModalComponent implements OnInit { this.authService.authenticate(response); this.closeButton.nativeElement.click(); + location.reload(); //(<HTMLSelectElement>document.getElementById('linkToLoginModal')).click(); }, (error) => console.warn(error)); } diff --git a/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts b/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts new file mode 100644 index 00000000..66b3755e --- /dev/null +++ b/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts @@ -0,0 +1,48 @@ +import { Component, OnInit } from '@angular/core'; +import { DatasetsService } from 'src/app/_services/datasets.service'; +import Dataset from 'src/app/_data/Dataset'; +import {Router} from '@angular/router' +import { JwtHelperService } from '@auth0/angular-jwt'; +import { CookieService } from 'ngx-cookie-service'; +import shared from 'src/app/Shared'; + +@Component({ + selector: 'app-filter-datasets', + templateUrl: './filter-datasets.component.html', + styleUrls: ['./filter-datasets.component.css'] +}) +export class FilterDatasetsComponent implements OnInit { + + shared = shared; + publicDatasets?: Dataset[]; + term: string = ""; + constructor(private datasets: DatasetsService,private router:Router, private cookie: CookieService) { + this.datasets.getPublicDatasets().subscribe((datasets) => { + this.publicDatasets = datasets; + }); + } + + ngOnInit(): void { + + } + addDataset(dataset: Dataset):void{ + //this.router.navigateByUrl('/predict?id='+id); + const helper = new JwtHelperService(); + const decodedToken = helper.decodeToken(this.cookie.get("token")); + const newDataset={...dataset}; + newDataset._id = ""; + newDataset.isPublic = false; + newDataset.lastUpdated = new Date(); + newDataset.uploaderId = decodedToken.uploaderId; + let name=prompt("Unesite naziv dataset-a",newDataset.name); + newDataset.name=name as string; + if(name!=null && name!="") + this.datasets.addDataset(newDataset).subscribe((response:string)=>{ + shared.openDialog("Obaveštenje", "Uspešno ste dodali dataset sa nazivom " + newDataset.name); + },(error)=>{ + shared.openDialog("Obaveštenje", "U svojoj kolekciji već imate dataset sa ovim imenom. Molimo Vas da unesete drugo ime."); + }); + + }; + +} diff --git a/frontend/src/app/_pages/my-models/my-models.component.html b/frontend/src/app/_pages/my-models/my-models.component.html new file mode 100644 index 00000000..9b281239 --- /dev/null +++ b/frontend/src/app/_pages/my-models/my-models.component.html @@ -0,0 +1,43 @@ +<div id="header"> + <h1>Moji modeli</h1> +</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 model of myModels"> + <app-item-model [model]="model"></app-item-model> + + <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)="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> + + + </div> + </div> + </div><!-- end panel-footer --> + + + + </div> + </div> + <div class="text-center" *ngIf="this.myModels.length == 0" > + <h2>Nema rezultata</h2> + </div> + </div> + + </div> + + + + + + </div> diff --git a/frontend/src/app/_pages/my-models/my-models.component.ts b/frontend/src/app/_pages/my-models/my-models.component.ts new file mode 100644 index 00000000..d379fa69 --- /dev/null +++ b/frontend/src/app/_pages/my-models/my-models.component.ts @@ -0,0 +1,59 @@ +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'; + +@Component({ + selector: 'app-my-models', + templateUrl: './my-models.component.html', + styleUrls: ['./my-models.component.css'] +}) +export class MyModelsComponent implements OnInit { + myModels: Model[] = []; + //myModel: Model; + + constructor(private modelsS : ModelsService, private router : Router) { + + + + } + + ngOnInit(): void { + this.getAllMyModels(); + + } +/* + editModel(): void{ + this.modelsS.editModel().subscribe(m => { + this.myModel = m; + + }) + } +*/ + +deleteThisModel(model: Model): void{ + shared.openYesNoDialog('Brisanje seta podataka','Da li ste sigurni da želite da obrišete model?',() => { + this.modelsS.deleteModel(model).subscribe((response) => { + this.getAllMyModels(); + }, (error) =>{ + if (error.error == "Model with name = {name} deleted") { + shared.openDialog("Obaveštenje", "Greška prilikom brisanja modela."); + } + }); + }); +} + + +useThisModel(model: Model): void{ + + this.router.navigate(['/training']) + +} + getAllMyModels(): void{ + this.modelsS.getMyModels().subscribe(m => { + this.myModels = m; + }); + } + +} diff --git a/frontend/src/app/_services/auth.service.ts b/frontend/src/app/_services/auth.service.ts index dd46615d..cc5ad688 100644 --- a/frontend/src/app/_services/auth.service.ts +++ b/frontend/src/app/_services/auth.service.ts @@ -22,7 +22,7 @@ export class AuthService { } register(user: any) { - return this.http.post(`${Configuration.settings.apiURL}/auth/register`, { ...user }, { responseType: 'text' }); + return this.http.post(`${Configuration.settings.apiURL}/auth/register`, { ...user },{ headers: this.authHeader() , responseType: 'text' }); } getGuestToken() { @@ -52,26 +52,19 @@ 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); }); }, exp.getTime() - new Date().getTime() - 60000); - } - else { - this.refresher = setTimeout(() => { - this.getGuestToken().subscribe((response) => { - this.authenticate(response); - }); - }, exp.getTime() - new Date().getTime() - 60000); - } + } addGuestToken() { this.getGuestToken().subscribe((token) => { this.authenticate(token); + location.reload(); }); } @@ -106,4 +99,15 @@ export class AuthService { authHeader() { return new HttpHeaders().set("Authorization", "Bearer " + this.cookie.get('token')); } + alreadyGuest(){ + if(this.cookie.check('token')){ + const token = this.cookie.get('token'); + const decodedToken = jwtHelper.decodeToken(token); + if(decodedToken.role=="Guest") + return true; + } + return false; + } + + } diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index f9bc2726..e301b46f 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -40,7 +40,8 @@ export class AppComponent implements OnInit, AfterViewInit { } }); if (!this.authService.isAuthenticated()) { - this.authService.addGuestToken(); + if(!this.authService.alreadyGuest()) + this.authService.addGuestToken(); } this.signalRService.startConnection(); //this.startHttpRequest(); diff --git a/frontend/src/app/experiment/experiment.component.html b/frontend/src/app/experiment/experiment.component.html new file mode 100644 index 00000000..62236cce --- /dev/null +++ b/frontend/src/app/experiment/experiment.component.html @@ -0,0 +1,255 @@ +<div id="header"> + <h1>Napravite svoju veštačku neuronsku mrežu</h1> +</div> +<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> + <mat-icon>arrow_forward</mat-icon> + <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="1">Preprocesiranje</a> + <mat-icon>arrow_forward</mat-icon> + <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="2">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 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" (change)="checkedColumnsChanged(item, 0); resetColumnEncodings();"> + <label class="form-check-label" for="cb_{{item.columnName}}"> + {{item.columnName}} + </label> + </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; checkedColumnsChanged(item, 1); resetColumnEncodings();" + checked> + <label class="form-check-label" for="rb_{{item.columnName}}"> + {{item.columnName}} + </label> + </div> + </div> + </div> + </div> + </div> + + + <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 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 + 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 + 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"> + 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> + + <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);"> + <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);"> + <option *ngFor="let option of column.uniqueValues" [value]="option"> + {{ option }} + </option> + </select> + </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" + [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 + redove + <input type="radio" [id]="'delRows_'+column.columnName" + [name]="'delOp_'+column.columnName" checked + (change)="emptyFillTextInput(column.columnName)"></label> + </div> + </div> + </div> + </div> + </div> + </div> + </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"> + <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="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"> + </div> + </div> + </div> + + <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 class="carousel-item mt-2"> + <h2 class="mb-4">3. Dodaj eskperiment</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> + <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> + </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> + </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 new file mode 100644 index 00000000..2d0f6ec5 --- /dev/null +++ b/frontend/src/app/experiment/experiment.component.ts @@ -0,0 +1,222 @@ +import { Component, OnInit } from '@angular/core'; +import Experiment, { NullValReplacer, NullValueOptions, ReplaceWith, Encoding } from '../_data/Experiment'; +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'; + +@Component({ + selector: 'app-experiment', + templateUrl: './experiment.component.html', + styleUrls: ['./experiment.component.css'] +}) +export class ExperimentComponent implements OnInit { + + experiment: Experiment = new Experiment(); + selectedModel?: Model; + selectedDataset?: Dataset; + trainingResult: any; // any za sad, promeni kasnije + + NullValueOptions = NullValueOptions; + ReplaceWith = ReplaceWith; + Encoding = Encoding; + ColumnEncoding = ColumnEncoding; + Object = Object; + ProblemType=ProblemType; + selectedColumnsInfoArray: ColumnInfo[] = []; + selectedNotNullColumnsArray: string[] = []; + + tempTestSetDistribution = 90; + + constructor(private modelsService: ModelsService, private experimentsService: ExperimentsService) { + } + + ngOnInit(): void { + } + + updateDataset(dataset: Dataset) { + this.selectedDataset = dataset; + this.selectedColumnsInfoArray = this.selectedDataset.columnInfo; + this.selectedNotNullColumnsArray = []; + this.experiment.outputColumn = this.selectedDataset.columnInfo[this.selectedDataset.columnInfo.length - 1].columnName; + + this.resetColumnEncodings(); + console.log(this.experiment.encodings); + } + + 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 { + return document.getElementById(id) as HTMLInputElement; + } + + arrayColumn = (arr: any[][], n: number) => [...this.dropEmptyString(new Set(arr.map(x => x[n])))]; + + dropEmptyString(set: Set<any>): Set<string> { + if (set.has("")) + set.delete(""); + if (set.has(null)) + set.delete(null); + if (set.has(undefined)) + set.delete(undefined); + return set; + } + + emptyFillTextInput(colName: string) { + (<HTMLInputElement>document.getElementById("fillText_" + colName)).value = ""; + } + + checkFillColRadio(colName: string) { + (<HTMLInputElement>document.getElementById("fillCol_" + colName)).checked = true; + } + + checkedColumnsChanged(checkedColumnInfo: ColumnInfo, buttonType: number) { //0-input,1-output + let col = this.selectedColumnsInfoArray.find(x => x.columnName == checkedColumnInfo.columnName); + if (buttonType == 0) { //inputCol + if (col == undefined) + this.selectedColumnsInfoArray.push(checkedColumnInfo); + else + this.selectedColumnsInfoArray = this.selectedColumnsInfoArray.filter(x => x.columnName != checkedColumnInfo.columnName); + } + else { //outputCol + if (col == undefined) //ako je vec cekiran neki output, samo dodaj sad ovaj, a taj output postaje input i ostaje u nizu + this.selectedColumnsInfoArray.push(checkedColumnInfo); + } + //console.log(this.selectedColumnsInfoArray); + } + + replace(event: Event, column: ColumnInfo) { + let option = (<HTMLInputElement>event.target).value; + + const input = (<HTMLInputElement>document.getElementById("fillText_" + column.columnName)); + if (column.isNumber) { + switch (option) { + case ReplaceWith.Max: + input.value = "" + column.max; + break; + case ReplaceWith.Min: + input.value = "" + column.min; + break; + case ReplaceWith.Mean: + input.value = "" + column.mean; + break; + case ReplaceWith.Median: + input.value = "" + column.median; + break; + case ReplaceWith.None: + break; + } + } else { + input.value = option; + } + } + + getSelectedColumnsArrayWithoutNullVals(): string[] { + let colNames: string[] = []; + + for (let i = 0; i < this.selectedColumnsInfoArray.length; i++) { + let oneColInfo = this.selectedColumnsInfoArray[i]; + if (oneColInfo.numNulls == 0) + colNames.push(oneColInfo.columnName); + } + return colNames; + } + + getNullValuesReplacersArray(): NullValReplacer[] { + let array: NullValReplacer[] = []; + + if (this.experiment.nullValues == NullValueOptions.Replace) { + + for (let i = 0; i < this.selectedColumnsInfoArray.length; i++) { + let oneColInfo = this.selectedColumnsInfoArray[i]; + + if (oneColInfo.numNulls > 0) { //ako kolona nema null vrednosti, ne dodajemo je u niz + if ((<HTMLInputElement>document.getElementById("delCol_" + oneColInfo.columnName)).checked) { + array.push({ + column: oneColInfo.columnName, + option: NullValueOptions.DeleteColumns, + value: "" + }); + } + else if ((<HTMLInputElement>document.getElementById("delRows_" + oneColInfo.columnName)).checked) { + array.push({ + column: oneColInfo.columnName, + option: NullValueOptions.DeleteRows, + value: "" + }); + } + else if (((<HTMLInputElement>document.getElementById("fillCol_" + oneColInfo.columnName)).checked)) { + array.push({ + column: oneColInfo.columnName, + option: NullValueOptions.Replace, + value: (<HTMLInputElement>document.getElementById("fillText_" + oneColInfo.columnName)).value + }); + } + } + } + } + return array; + } + + saveExperiment() { + if (this.selectedDataset == undefined) { + Shared.openDialog("Greška", "Izvor podataka nije izabran!"); + return; + } + if (this.experiment.outputColumn == '') { + Shared.openDialog("Greška", "Molimo Vas da izaberete izlaznu kolonu."); + return; + } + if (this.selectedColumnsInfoArray.length <= 1) { //jer izlazna je izabrana + Shared.openDialog("Greška", "Molimo Vas da izaberete ulazne kolone."); + return; + } + + this.experiment._id = ''; + this.experiment.uploaderId = ''; + this.experiment.datasetId = this.selectedDataset._id; + + let pom = this.selectedColumnsInfoArray.filter(x => x.columnName != this.experiment.outputColumn); + for (let i = 0; i < pom.length; i++) + this.experiment.inputColumns.push(pom[i].columnName); + + this.selectedColumnsInfoArray = this.selectedColumnsInfoArray.filter(x => x.numNulls > 0); //obavezno + this.experiment.nullValuesReplacers = this.getNullValuesReplacersArray(); + + this.experiment.randomTestSetDistribution = 1 - Math.round(this.tempTestSetDistribution / 100 * 10) / 10; + + console.log("Eksperiment:", this.experiment); + + this.experimentsService.addExperiment(this.experiment).subscribe((response) => { + this.experiment = response; + + this.selectedColumnsInfoArray = []; + this.selectedNotNullColumnsArray = []; + this.experiment.encodings = []; + + Shared.openDialog("Obaveštenje", "Eksperiment je uspešno kreiran."); + }, (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."); + } + }); + } + + countSelectedNullCols(): number { + let counter: number = 0; + + for (let i = 0; i < this.selectedColumnsInfoArray.length; i++) { + let oneColInfo = this.selectedColumnsInfoArray[i]; + if (oneColInfo.numNulls > 0) + ++counter; + } + return counter; + } +} diff --git a/frontend/src/app/training/training.component.html b/frontend/src/app/training/training.component.html new file mode 100644 index 00000000..672e75fb --- /dev/null +++ b/frontend/src/app/training/training.component.html @@ -0,0 +1,46 @@ +<div id="header"> + <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 class="col-10"> + + <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)" [forExperiment]="selectedExperiment"></app-model-load> + + <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 + 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> + </div> +</div>
\ No newline at end of file |