aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--backend/api/api/Controllers/AuthController.cs32
-rw-r--r--backend/api/api/Controllers/DatasetController.cs12
-rw-r--r--backend/api/api/Controllers/ExperimentController.cs4
-rw-r--r--backend/api/api/Controllers/ModelController.cs12
-rw-r--r--backend/api/api/Controllers/PredictorController.cs10
-rw-r--r--backend/api/api/Interfaces/IAuthService.cs2
-rw-r--r--backend/api/api/Interfaces/IJwtToken.cs4
-rw-r--r--backend/api/api/Models/User.cs4
-rw-r--r--backend/api/api/Services/AuthService.cs16
-rw-r--r--backend/api/api/Services/JwtToken.cs25
-rw-r--r--backend/api/api/Services/TempRemovalService.cs69
-rw-r--r--frontend/src/app/_data/Experiment.ts1
-rw-r--r--frontend/src/app/_elements/dataset-load/dataset-load.component.ts100
-rw-r--r--frontend/src/app/_elements/model-load/model-load.component.html215
-rw-r--r--frontend/src/app/_elements/model-load/model-load.component.ts114
-rw-r--r--frontend/src/app/_modals/login-modal/login-modal.component.ts2
-rw-r--r--frontend/src/app/_modals/register-modal/register-modal.component.ts1
-rw-r--r--frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts48
-rw-r--r--frontend/src/app/_pages/my-models/my-models.component.html43
-rw-r--r--frontend/src/app/_pages/my-models/my-models.component.ts59
-rw-r--r--frontend/src/app/_services/auth.service.ts24
-rw-r--r--frontend/src/app/app.component.ts3
-rw-r--r--frontend/src/app/experiment/experiment.component.html255
-rw-r--r--frontend/src/app/experiment/experiment.component.ts222
-rw-r--r--frontend/src/app/training/training.component.html46
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();">&nbsp;
+ <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>&nbsp;
+ <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}}&nbsp;
+ <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:&nbsp;&nbsp;
+ <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