diff options
56 files changed, 1687 insertions, 313 deletions
diff --git a/backend/api/api/Controllers/FileController.cs b/backend/api/api/Controllers/FileController.cs index 0fe8415b..d29c5676 100644 --- a/backend/api/api/Controllers/FileController.cs +++ b/backend/api/api/Controllers/FileController.cs @@ -4,6 +4,7 @@ using api.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; + namespace api.Controllers { [Route("api/[controller]")] @@ -11,6 +12,7 @@ namespace api.Controllers public class FileController : ControllerBase { private string[] permittedExtensions = { ".csv" }; + private string[] permittedExtensionsH5 = { ".h5" };//niz da bi dodali h4 itd private readonly IConfiguration _configuration; private IJwtToken _token; private IFileService _fileservice; @@ -22,6 +24,77 @@ namespace api.Controllers } + [HttpPost("h5")] + [Authorize(Roles = "User,Guest")] + public async Task<ActionResult<string>> H5Upload([FromForm] IFormFile file) + { + + //get username from jwtToken + string uploaderId; + string folderName; + var header = Request.Headers[HeaderNames.Authorization]; + if (AuthenticationHeaderValue.TryParse(header, out var headerValue)) + { + + var scheme = headerValue.Scheme; + var parameter = headerValue.Parameter; + uploaderId = _token.TokenToId(parameter); + if (uploaderId == null) + return null; + } + else + return BadRequest(); + if (uploaderId == "") + { + folderName = "TempFiles"; + } + else + { + folderName = "UploadedFiles"; + } + + + //Check filetype + var filename = file.FileName; + var ext = Path.GetExtension(filename).ToLowerInvariant(); + var name = Path.GetFileNameWithoutExtension(filename).ToLowerInvariant(); + if (string.IsNullOrEmpty(ext) || !permittedExtensionsH5.Contains(ext)) + { + return BadRequest("Wrong file type"); + } + var folderPath = Path.Combine(Directory.GetCurrentDirectory(), folderName, uploaderId); + //Check Directory + if (!Directory.Exists(folderPath)) + { + Directory.CreateDirectory(folderPath); + } + //Index file if same filename + var fullPath = Path.Combine(folderPath, filename); + int i = 0; + + while (System.IO.File.Exists(fullPath)) + { + i++; + fullPath = Path.Combine(folderPath, name + i.ToString() + ext); + } + + + //Write file + using (var stream = new FileStream(fullPath, FileMode.Create)) + { + await file.CopyToAsync(stream); + } + FileModel fileModel = new FileModel(); + fileModel.type = "h5"; + fileModel.path = fullPath; + fileModel.uploaderId = uploaderId; + fileModel.date = DateTime.Now.ToUniversalTime(); + fileModel = _fileservice.Create(fileModel); + + + return Ok(fileModel); + } + [HttpPost("Csv")] [Authorize(Roles = "User,Guest")] @@ -81,6 +154,7 @@ namespace api.Controllers await file.CopyToAsync(stream); } FileModel fileModel= new FileModel(); + fileModel.type = "csv"; fileModel.path=fullPath; fileModel.uploaderId= uploaderId; fileModel.date = DateTime.Now.ToUniversalTime(); @@ -90,6 +164,35 @@ namespace api.Controllers return Ok(fileModel); } + + //msm generalno moze da se koristi Download samo + [HttpGet("downloadh5")] + [Authorize(Roles = "User,Guest")] + public async Task<ActionResult> DownloadH5(string id) + { + //Get Username + string uploaderId; + var header = Request.Headers[HeaderNames.Authorization]; + if (AuthenticationHeaderValue.TryParse(header, out var headerValue)) + { + + var scheme = headerValue.Scheme; + var parameter = headerValue.Parameter; + uploaderId = _token.TokenToId(parameter); + if (uploaderId == null) + return null; + } + else + return BadRequest(); + + string filePath = _fileservice.GetFilePath(id, uploaderId); + if (filePath == null) + return BadRequest(); + + return File(System.IO.File.ReadAllBytes(filePath), "application/octet-stream", Path.GetFileName(filePath)); + + } + [HttpGet("Download")] [Authorize(Roles = "User,Guest")] public async Task<ActionResult> DownloadFile(string id) diff --git a/backend/api/api/Controllers/PredictorController.cs b/backend/api/api/Controllers/PredictorController.cs index cdc14632..161271e2 100644 --- a/backend/api/api/Controllers/PredictorController.cs +++ b/backend/api/api/Controllers/PredictorController.cs @@ -77,7 +77,7 @@ namespace api.Controllers // GET api/<PredictorController>/getpredictor/{name} [HttpGet("getpredictor/{id}")] - [Authorize(Roles = "User")] + [Authorize(Roles = "User,Guest")] public ActionResult<Predictor> GetPredictor(string id) { string username; @@ -188,8 +188,8 @@ namespace api.Controllers // POST api/<PredictorController>/usepredictor {predictor,inputs} [HttpPost("usepredictor/{id}")] - [Authorize(Roles = "User")] - public ActionResult UsePredictor(String id, [FromBody] String[] inputs) + [Authorize(Roles = "User,Guest")] + public ActionResult UsePredictor(String id, [FromBody] PredictorColumns[] inputs) { string username; @@ -207,8 +207,8 @@ namespace api.Controllers Predictor predictor = _predictorService.GetPredictor(username, id); - foreach(String i in inputs) - Debug.WriteLine(i); + foreach(PredictorColumns i in inputs) + Debug.WriteLine(i.value.ToString()); return NoContent(); } diff --git a/backend/api/api/Models/FileModel.cs b/backend/api/api/Models/FileModel.cs index 1043309d..47b12110 100644 --- a/backend/api/api/Models/FileModel.cs +++ b/backend/api/api/Models/FileModel.cs @@ -8,6 +8,7 @@ namespace api.Models [BsonId] [BsonRepresentation(BsonType.ObjectId)] public string _id { get; set; } + public string type { get; set; } public string uploaderId { get; set; } public string path { get; set; } [BsonDateTimeOptions(Kind = DateTimeKind.Utc)] diff --git a/backend/api/api/Models/PredictorColumns.cs b/backend/api/api/Models/PredictorColumns.cs new file mode 100644 index 00000000..82f3e979 --- /dev/null +++ b/backend/api/api/Models/PredictorColumns.cs @@ -0,0 +1,8 @@ +namespace api.Models +{ + public class PredictorColumns + { + public String name { get; set; } + public String value { get; set; } + } +} diff --git a/backend/api/api/Services/PredictorService.cs b/backend/api/api/Services/PredictorService.cs index 01bc8359..b15255ac 100644 --- a/backend/api/api/Services/PredictorService.cs +++ b/backend/api/api/Services/PredictorService.cs @@ -42,7 +42,7 @@ namespace api.Services } public Predictor GetPredictor(string username, string id) { - return _predictor.Find(predictor => predictor.username == username && predictor._id == id).FirstOrDefault(); + return _predictor.Find(predictor => predictor._id == id && (predictor.username == username || predictor.isPublic == true)).FirstOrDefault(); } //last private models diff --git a/backend/api/api/Services/UserService.cs b/backend/api/api/Services/UserService.cs index 7ec6f4b2..7fc4bdb1 100644 --- a/backend/api/api/Services/UserService.cs +++ b/backend/api/api/Services/UserService.cs @@ -50,7 +50,7 @@ namespace api.Services //username koji postoji u bazi using (var session = _client.StartSession()) { - + if(username!=user.Username) if(_users.Find(u => u.Username == user.Username).FirstOrDefault()!=null) { return false; diff --git a/backend/microservice/__pycache__/mlservice.cpython-310.pyc b/backend/microservice/__pycache__/mlservice.cpython-310.pyc Binary files differindex c079459a..ac93f3db 100644 --- a/backend/microservice/__pycache__/mlservice.cpython-310.pyc +++ b/backend/microservice/__pycache__/mlservice.cpython-310.pyc diff --git a/backend/microservice/api.py b/backend/microservice/api.py index 4768f34c..9a28b159 100644 --- a/backend/microservice/api.py +++ b/backend/microservice/api.py @@ -9,7 +9,7 @@ import csv import json import mlservice import h5py -from mlservice2 import unositok +from mlservice import unositok app = flask.Flask(__name__) diff --git a/backend/microservice/api/controller.py b/backend/microservice/api/controller.py index 059af317..1b17f727 100644 --- a/backend/microservice/api/controller.py +++ b/backend/microservice/api/controller.py @@ -1,7 +1,7 @@ import flask from flask import request, jsonify import ml_socket -import ml_service +import newmlservice import tensorflow as tf import pandas as pd @@ -25,7 +25,7 @@ def train(): f = request.json["dataset"] dataset = pd.read_csv(f) # - result = ml_service.train(dataset, request.json["model"], train_callback) + result = newmlservice.train(dataset, request.json["model"], train_callback) print(result) return jsonify(result) @@ -34,10 +34,22 @@ def predict(): f = request.json['filepath'] dataset = pd.read_csv(f) m = request.json['modelpath'] - #model = tf.keras.models.load_model(m) - # - #model.predict? + model = tf.keras.models.load_model(m) + print("********************************model loaded*******************************") + newmlservice.manageH5(dataset,request.json['model'],model) + return "done" + +@app.route('/preprocess',methods=['POST']) +def returnColumnsInfo(): + f=request.json['filepathcolinfo'] + dataset=pd.read_csv(f) + + result=newmlservice.returnColumnsInfo(dataset) + + return jsonify(result) + + print("App loaded.") ml_socket.start() app.run()
\ No newline at end of file diff --git a/backend/microservice/api/ml_service.py b/backend/microservice/api/ml_service.py index ea562212..0aed3dc9 100644 --- a/backend/microservice/api/ml_service.py +++ b/backend/microservice/api/ml_service.py @@ -1,4 +1,8 @@ +from cmath import nan +from enum import unique +from itertools import count import pandas as pd +from sklearn import datasets import tensorflow as tf import keras import numpy as np @@ -11,12 +15,67 @@ from typing_extensions import Self from copyreg import constructor from flask import request, jsonify, render_template from sklearn.preprocessing import LabelEncoder +from sklearn.preprocessing import OrdinalEncoder +import category_encoders as ce from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split from dataclasses import dataclass +import statistics as s +from sklearn.metrics import roc_auc_score + +def returnColumnsInfo(dataset): + dict=[] + datafront=dataset.copy() + svekolone=datafront.columns + kategorijskekolone=datafront.select_dtypes(include=['object']).columns + allNullCols=0 + for kolona in svekolone: + if(kolona in kategorijskekolone): + uniquevalues=datafront[kolona].unique() + mean=0 + median=0 + min=0 + max=0 + nullCount=datafront[kolona].isnull().sum() + if(nullCount>0): + allNullCols=allNullCols+1 + frontreturn={'columnName':kolona, + 'isNumber':False, + 'uniqueValues':uniquevalues.tolist(), + 'mean':float(mean), + 'median':float(median), + 'numNulls':float(nullCount), + 'min':min, + 'max':max + } + dict.append(frontreturn) + else: + mean=datafront[kolona].mean() + median=s.median(datafront[kolona]) + nullCount=datafront[kolona].isnull().sum() + min=min(datafront[kolona]) + max=max(datafront[kolona]) + if(nullCount>0): + allNullCols=allNullCols+1 + frontreturn={'columnName':kolona, + 'isNumber':1, + 'uniqueValues':[], + 'mean':float(mean), + 'median':float(median), + 'numNulls':float(nullCount), + 'min':min, + 'max':max + } + dict.append(frontreturn) + NullRows = datafront[datafront.isnull().any(axis=1)] + #print(NullRows) + #print(len(NullRows)) + allNullRows=len(NullRows) + + return {'columnInfo':dict,'allNullColl':allNullCols,'allNullRows':allNullRows} @dataclass -class TrainingResult: +class TrainingResultClassification: accuracy: float precision: float recall: float @@ -26,18 +85,29 @@ class TrainingResult: tp: float specificity: float f1: float + logloss: float + fpr: float + tpr: float + metrics: dict +''' +@datasets +class TrainingResultRegression: mse: float mae: float mape: float rmse: float - fpr: float - tpr: float +@dataclass +class TrainingResult: + metrics: dict +''' def train(dataset, params, callback): problem_type = params["type"] data = pd.DataFrame() for col in params["inputColumns"]: data[col]=dataset[col] + + print(data.head()) output_column = params["columnToPredict"] data[output_column] = dataset[output_column] # @@ -66,6 +136,7 @@ def train(dataset, params, callback): data.pop(col) # # Enkodiranje + # https://www.analyticsvidhya.com/blog/2020/08/types-of-categorical-data-encoding/ # encoding=params["encoding"] if(encoding=='label'): @@ -79,6 +150,34 @@ def train(dataset, params, callback): if(data[col].dtype==np.object_): category_columns.append(col) data=pd.get_dummies(data, columns=category_columns, prefix=category_columns) + elif(encoding=='ordinal'): + encoder = OrdinalEncoder() + for col in data.columns: + if(data[col].dtype==np.object_): + data[col]=encoder.fit_transform(data[col]) + + elif(encoding=='hashing'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + encoder=ce.HashingEncoder(cols=category_columns, n_components=len(category_columns)) + encoder.fit_transform(data) + elif(encoding=='binary'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + encoder=ce.BinaryEncoder(cols=category_columns, return_df=True) + encoder.fit_transform(data) + + elif(encoding=='baseN'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + encoder=ce.BaseNEncoder(cols=category_columns, return_df=True, base=5) + encoder.fit_transform(data) # # Input - output # @@ -88,71 +187,271 @@ def train(dataset, params, callback): x_columns.append(col) x = data[x_columns].values y = data[output_column].values + print(x_columns) + print(x) # # Podela na test i trening skupove # test=params["randomTestSetDistribution"] randomOrder = params["randomOrder"] if(randomOrder): - random=50 + random=123 else: random=0 - x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=test, random_state=random) + x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.5,random_state=0) # # Skaliranje vrednosti # + ''' scaler=StandardScaler() scaler.fit(x_train) x_test=scaler.transform(x_test) x_train=scaler.transform(x_train) + ''' + # # Treniranje modela # - classifier=tf.keras.Sequential() + # hidden_layer_neurons = params["hiddenLayerNeurons"] - for func in params["hiddenLayerActivationFunctions"]: - classifier.add(tf.keras.layers.Dense(units=hidden_layer_neurons,activation=func)) - output_func = params["outputLayerActivationFunction"] - classifier.add(tf.keras.layers.Dense(units=1,activation=output_func)) - optimizer = params["optimizer"] - metrics=params['metrics'] - loss_func=params["lossFunction"] - classifier.compile(optimizer=optimizer, loss=loss_func,metrics=metrics) - batch_size = params["batchSize"] - epochs = params["epochs"] - history=classifier.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, callbacks=callback(x_test, y_test), validation_split=0.2) # TODO params["validationSplit"] + + if(problem_type=='multi-klasifikacioni'): + func=params['hiddenLayerActivationFunctions'] + output_func = params["outputLayerActivationFunction"] + optimizer = params["optimizer"] + metrics=params['metrics'] + loss_func=params["lossFunction"] + batch_size = params["batchSize"] + epochs = params["epochs"] + inputDim = len(data.columns) - 1 + ''' + classifier=tf.keras.Sequential() + + classifier.add(tf.keras.layers.Dense(units=len(data.columns),input_dim=inputDim))#input layer + + for f in func:#hidden layers + classifier.add(tf.keras.layers.Dense(hidden_layer_neurons,activation=f)) + + numberofclasses=len(output_column.unique()) + classifier.add(tf.keras.layers.Dense(numberofclasses,activation=output_func))#output layer + ''' + model=tf.keras.Sequential() + model.add(tf.keras.layers.Dense(1,input_dim=x_train.shape[1]))#input layer + model.add(tf.keras.layers.Dense(1, activation='sigmoid')) + model.add(tf.keras.layers.Dense(len(output_column.unique())+1, activation='softmax')) + classifier.compile(optimizer=optimizer, loss=loss_func,metrics=metrics) + + history=classifier.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, callbacks=callback(x_test, y_test)) + else: + classifier=tf.keras.Sequential() + + for func in params["hiddenLayerActivationFunctions"]: + classifier.add(tf.keras.layers.Dense(units=hidden_layer_neurons,activation=func)) + output_func = params["outputLayerActivationFunction"] + + if(problem_type!="regresioni"): + classifier.add(tf.keras.layers.Dense(units=1,activation=output_func)) + else: + classifier.add(tf.keras.layers.Dense(units=1)) + + optimizer = params["optimizer"] + metrics=params['metrics'] + loss_func=params["lossFunction"] + classifier.compile(optimizer=optimizer, loss=loss_func,metrics=metrics) + batch_size = params["batchSize"] + epochs = params["epochs"] + history=classifier.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, callbacks=callback(x_test, y_test), validation_split=0.2) # TODO params["validationSplit"] # # Test # model_name = params['_id'] - y_pred=classifier.predict(x_test) + #y_pred=classifier.predict(x_test) if(problem_type == "regresioni"): - classifier.evaluate(x_test, y_test) - elif(problem_type == "binarni-klasifikacioni"): + y_pred=classifier.predict(x_test) + print(classifier.evaluate(x_test, y_test)) + elif(problem_type == "binarni-klasifikacioni"): + y_pred=classifier.predict(x_test) y_pred=(y_pred>=0.5).astype('int') + elif(problem_type=='multi-klasifikacioni'): + y_pred=classifier.predict(x_test) + y_pred=np.argmax(y_pred,axis=1) + y_pred=y_pred.flatten() result=pd.DataFrame({"Actual":y_test,"Predicted":y_pred}) classifier.save("temp/"+model_name, save_format='h5') + # ROC multi-klasifikacioni + def roc_auc_score_multiclass(actual_class, pred_class, average = "macro"): + + #creating a set of all the unique classes using the actual class list + unique_class = set(actual_class) + roc_auc_dict = {} + for per_class in unique_class: + + #creating a list of all the classes except the current class + other_class = [x for x in unique_class if x != per_class] + + #marking the current class as 1 and all other classes as 0 + new_actual_class = [0 if x in other_class else 1 for x in actual_class] + new_pred_class = [0 if x in other_class else 1 for x in pred_class] + + #using the sklearn metrics method to calculate the roc_auc_score + roc_auc = roc_auc_score(new_actual_class, new_pred_class, average = average) + roc_auc_dict[per_class] = roc_auc + + return roc_auc_dict # # Metrike # print("HELLO???") print(result) print("HELLO???") - accuracy = float(sm.accuracy_score(y_test,y_pred)) - precision = float(sm.precision_score(y_test,y_pred)) - recall = float(sm.recall_score(y_test,y_pred)) - tn, fp, fn, tp = sm.confusion_matrix(y_test,y_pred).ravel() - specificity = float(tn / (tn+fp)) - f1 = float(sm.f1_score(y_test,y_pred)) - mse = float(sm.mean_squared_error(y_test,y_pred)) - mae = float(sm.mean_absolute_error(y_test,y_pred)) - mape = float(sm.mean_absolute_percentage_error(y_test,y_pred)) - rmse = float(np.sqrt(sm.mean_squared_error(y_test,y_pred))) - fpr, tpr, _ = sm.roc_curve(y_test,y_pred) + if(problem_type=="binarni-klasifikacioni"): + accuracy = float(sm.accuracy_score(y_test,y_pred)) + precision = float(sm.precision_score(y_test,y_pred)) + recall = float(sm.recall_score(y_test,y_pred)) + tn, fp, fn, tp = sm.confusion_matrix(y_test,y_pred).ravel() + specificity = float(tn / (tn+fp)) + f1 = float(sm.f1_score(y_test,y_pred)) + fpr, tpr, _ = sm.roc_curve(y_test,y_pred) + logloss = float(sm.log_loss(y_test, y_pred)) + metrics= {"accuracy" : accuracy, + "precision" : precision, + "recall" : recall, + "specificity" : specificity, + "f1" : f1, + "tn" : float(tn), + "fp" : float(fp), + "fn" : float(fn), + "tp" : float(tp), + "fpr" : fpr.tolist(), + "tpr" : tpr.tolist(), + "logloss" : logloss + } + elif(problem_type=="regresioni"): + # https://www.analyticsvidhya.com/blog/2021/05/know-the-best-evaluation-metrics-for-your-regression-model/ + mse = float(sm.mean_squared_error(y_test,y_pred)) + mae = float(sm.mean_absolute_error(y_test,y_pred)) + mape = float(sm.mean_absolute_percentage_error(y_test,y_pred)) + rmse = float(np.sqrt(sm.mean_squared_error(y_test,y_pred))) + rmsle = float(np.sqrt(sm.mean_squared_error(y_test, y_pred))) + r2 = float(sm.r2_score(y_test, y_pred)) + # n - num of observations + # k - num of independent variables + n = 40 + k = 2 + adj_r2 = float(1 - ((1-r2)*(n-1)/(n-k-1))) + metrics= {"mse" : mse, + "mae" : mae, + "mape" : mape, + "rmse" : rmse, + "rmsle" : rmsle, + "r2" : r2, + "adj_r2" : adj_r2 + } + elif(problem_type=="multi-klasifikacioni"): + + cr=sm.classification_report(y_test, y_pred) + cm=sm.confusion_matrix(y_test,y_pred) + # https://www.kaggle.com/code/nkitgupta/evaluation-metrics-for-multi-class-classification/notebook + accuracy=metrics.accuracy_score(y_test, y_pred) + macro_averaged_precision=metrics.precision_score(y_test, y_pred, average = 'macro') + micro_averaged_precision=metrics.precision_score(y_test, y_pred, average = 'micro') + macro_averaged_recall=metrics.recall_score(y_test, y_pred, average = 'macro') + micro_averaged_recall=metrics.recall_score(y_test, y_pred, average = 'micro') + macro_averaged_f1=metrics.f1_score(y_test, y_pred, average = 'macro') + micro_averaged_f1=metrics.f1_score(y_test, y_pred, average = 'micro') + roc_auc_dict=roc_auc_score_multiclass(y_test, y_pred) + + # TODO upload trenirani model nazad na backend - return TrainingResult(accuracy, precision, recall, float(tn), float(fp), float(fn), float(tp), specificity, f1, mse, mae, mape, rmse, fpr.tolist(), tpr.tolist()) + #return TrainingResult(metrics) +def manageH5(datain,params,h5model): + dataset=datain.copy() + problem_type = params["type"] + data = pd.DataFrame() + for col in params["inputColumns"]: + data[col]=dataset[col] + output_column = params["columnToPredict"] + data[output_column] = dataset[output_column] + # + # Brisanje null kolona / redova / zamena + #nullreplace=[ + # {"column":"Embarked","value":"C","deleteRow":false,"deleteCol":true}, + # {"column": "Cabin","value":"C123","deleteRow":"0","deleteCol":"0"}] + + null_value_options = params["nullValues"] + null_values_replacers = params["nullValuesReplacers"] + + if(null_value_options=='replace'): + print("replace null") # TODO + elif(null_value_options=='delete_rows'): + data=data.dropna() + elif(null_value_options=='delete_columns'): + data=data.dropna() + # + #print(data.isnull().any()) + # + # Brisanje kolona koje ne uticu na rezultat + # + num_rows=data.shape[0] + for col in data.columns: + if((data[col].nunique()==(num_rows)) and (data[col].dtype==np.object_)): + data.pop(col) + # + # Enkodiranje + # https://www.analyticsvidhya.com/blog/2020/08/types-of-categorical-data-encoding/ + # + encoding=params["encoding"] + if(encoding=='label'): + encoder=LabelEncoder() + for col in data.columns: + if(data[col].dtype==np.object_): + data[col]=encoder.fit_transform(data[col]) + elif(encoding=='onehot'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + data=pd.get_dummies(data, columns=category_columns, prefix=category_columns) + elif(encoding=='ordinal'): + encoder = OrdinalEncoder() + for col in data.columns: + if(data[col].dtype==np.object_): + data[col]=encoder.fit_transform(data[col]) + elif(encoding=='hashing'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + encoder=ce.HashingEncoder(cols=category_columns, n_components=len(category_columns)) + encoder.fit_transform(data) + elif(encoding=='binary'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + encoder=ce.BinaryEncoder(cols=category_columns, return_df=True) + encoder.fit_transform(data) + elif(encoding=='baseN'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + encoder=ce.BaseNEncoder(cols=category_columns, return_df=True, base=5) + encoder.fit_transform(data) + # + # Input - output + # + x_columns = [] + for col in data.columns: + if(col!=output_column): + x_columns.append(col) + x = data[x_columns].values + y = data[output_column].values + + + y_pred=h5model.predict_classes(x)
\ No newline at end of file diff --git a/backend/microservice/api/newmlservice.py b/backend/microservice/api/newmlservice.py new file mode 100644 index 00000000..50af15f8 --- /dev/null +++ b/backend/microservice/api/newmlservice.py @@ -0,0 +1,424 @@ +from enum import unique +from itertools import count +import pandas as pd +from sklearn import datasets, multiclass +import tensorflow as tf +import keras +import numpy as np +import csv +import json +import h5py +import sklearn.metrics as sm +from statistics import mode +from typing_extensions import Self +from copyreg import constructor +from flask import request, jsonify, render_template +from sklearn.preprocessing import LabelEncoder, MinMaxScaler +from sklearn.preprocessing import OrdinalEncoder +import category_encoders as ce +from sklearn.preprocessing import StandardScaler +from sklearn.model_selection import train_test_split +from dataclasses import dataclass +import statistics as s +from sklearn.metrics import roc_auc_score +from ann_visualizer.visualize import ann_viz; +def returnColumnsInfo(dataset): + dict=[] + datafront=dataset.copy() + svekolone=datafront.columns + kategorijskekolone=datafront.select_dtypes(include=['object']).columns + allNullCols=0 + for kolona in svekolone: + if(kolona in kategorijskekolone): + uniquevalues=datafront[kolona].unique() + mean=0 + median=0 + nullCount=datafront[kolona].isnull().sum() + if(nullCount>0): + allNullCols=allNullCols+1 + frontreturn={'columnName':kolona, + 'isNumber':False, + 'uniqueValues':uniquevalues.tolist(), + 'median':float(mean), + 'mean':float(median), + 'numNulls':float(nullCount) + } + dict.append(frontreturn) + else: + mean=datafront[kolona].mean() + median=s.median(datafront[kolona]) + nullCount=datafront[kolona].isnull().sum() + if(nullCount>0): + allNullCols=allNullCols+1 + frontreturn={'columnName':kolona, + 'isNumber':1, + 'uniqueValues':[], + 'mean':float(mean), + 'median':float(median), + 'numNulls':float(nullCount) + } + dict.append(frontreturn) + NullRows = datafront[datafront.isnull().any(axis=1)] + #print(NullRows) + #print(len(NullRows)) + allNullRows=len(NullRows) + + return {'columnInfo':dict,'allNullColl':allNullCols,'allNullRows':allNullRows} + +@dataclass +class TrainingResultClassification: + accuracy: float + precision: float + recall: float + tn: float + fp: float + fn: float + tp: float + specificity: float + f1: float + logloss: float + fpr: float + tpr: float + metrics: dict +''' +@datasets +class TrainingResultRegression: + mse: float + mae: float + mape: float + rmse: float + +@dataclass +class TrainingResult: + metrics: dict +''' + +def train(dataset, params, callback): + problem_type = params["type"] + print(problem_type) + data = pd.DataFrame() + print(data) + for col in params["inputColumns"]: + print(col) + data[col]=dataset[col] + output_column = params["columnToPredict"] + data[output_column] = dataset[output_column] + print(data) + + ###NULL + null_value_options = params["nullValues"] + null_values_replacers = params["nullValuesReplacers"] + + if(null_value_options=='replace'): + print("replace null") # TODO + elif(null_value_options=='delete_rows'): + data=data.dropna() + elif(null_value_options=='delete_columns'): + data=data.dropna() + print(data.shape) + + # + # Brisanje kolona koje ne uticu na rezultat + # + num_rows=data.shape[0] + for col in data.columns: + if((data[col].nunique()==(num_rows)) and (data[col].dtype==np.object_)): + data.pop(col) + # + ### Enkodiranje + encoding=params["encoding"] + if(encoding=='label'): + encoder=LabelEncoder() + for col in data.columns: + if(data[col].dtype==np.object_): + data[col]=encoder.fit_transform(data[col]) + + + elif(encoding=='onehot'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + data=pd.get_dummies(data, columns=category_columns, prefix=category_columns) + + elif(encoding=='ordinal'): + encoder = OrdinalEncoder() + for col in data.columns: + if(data[col].dtype==np.object_): + data[col]=encoder.fit_transform(data[col]) + + elif(encoding=='hashing'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + encoder=ce.HashingEncoder(cols=category_columns, n_components=len(category_columns)) + encoder.fit_transform(data) + elif(encoding=='binary'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + encoder=ce.BinaryEncoder(cols=category_columns, return_df=True) + encoder.fit_transform(data) + + elif(encoding=='baseN'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + encoder=ce.BaseNEncoder(cols=category_columns, return_df=True, base=5) + encoder.fit_transform(data) + # + # Input - output + # + x_columns = [] + for col in data.columns: + if(col!=output_column): + x_columns.append(col) + print(x_columns) + x = data[x_columns].values + y = data[output_column].values + + # + # Podela na test i trening skupove + # + test=params["randomTestSetDistribution"] + randomOrder = params["randomOrder"] + if(randomOrder): + random=123 + else: + random=0 + x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=test, random_state=random) + print(x_train,x_test) + + # + # Treniranje modela + # + # + if(problem_type=='multi-klasifikacioni'): + #print('multi') + classifier=tf.keras.Sequential() + + classifier.add(tf.keras.layers.Dense(units=params['hiddenLayerNeurons'], activation=params['hiddenLayerActivationFunctions'][0],input_dim=x_train.shape[1]))#prvi skriveni + definisanje prethodnog-ulaznog + for i in range(params['hiddenLayers']-1):#ako postoji vise od jednog skrivenog sloja + #print(i) + classifier.add(tf.keras.layers.Dense(units=params['hiddenLayerNeurons'], activation=params['hiddenLayerActivationFunctions'][i+1]))#i-ti skriveni sloj + classifier.add(tf.keras.layers.Dense(units=5, activation=params['outputLayerActivationFunction']))#izlazni sloj + + classifier.compile(loss =params["lossFunction"] , optimizer = params['optimizer'] , metrics =params['metrics']) + + history=classifier.fit(x_train, y_train, epochs = params['epochs'],batch_size=params['batchSize']) + + y_pred=classifier.predict(x_test) + y_pred=np.argmax(y_pred,axis=1) + #print(y_pred.flatten()) + #print(y_test) + scores = classifier.evaluate(x_test, y_test) + print("\n%s: %.2f%%" % (classifier.metrics_names[1], scores[1]*100)) + classifier.save("temp/"+params['name'], save_format='h5') + #vizuelizacija u python-u + #from ann_visualizer.visualize import ann_viz; + #ann_viz(classifier, title="My neural network") + + elif(problem_type=='binarni-klasifikacioni'): + #print('*************************************************************************binarni') + classifier=tf.keras.Sequential() + + classifier.add(tf.keras.layers.Dense(units=params['hiddenLayerNeurons'], activation=params['hiddenLayerActivationFunctions'][0],input_dim=x_train.shape[1]))#prvi skriveni + definisanje prethodnog-ulaznog + for i in range(params['hiddenLayers']-1):#ako postoji vise od jednog skrivenog sloja + #print(i) + classifier.add(tf.keras.layers.Dense(units=params['hiddenLayerNeurons'], activation=params['hiddenLayerActivationFunctions'][i+1]))#i-ti skriveni sloj + classifier.add(tf.keras.layers.Dense(units=1, activation=params['outputLayerActivationFunction']))#izlazni sloj + + classifier.compile(loss =params["lossFunction"] , optimizer = params['optimizer'] , metrics =params['metrics']) + + history=classifier.fit(x_train, y_train, epochs = params['epochs'],batch_size=params['batchSize']) + + y_pred=classifier.predict(x_test) + y_pred=(y_pred>=0.5).astype('int') + + print(y_pred.flatten()) + print(y_test) + + scores = classifier.evaluate(x_test, y_test) + print("\n%s: %.2f%%" % (classifier.metrics_names[1], scores[1]*100)) + #ann_viz(classifier, title="My neural network") + + classifier.save("temp/"+params['name'], save_format='h5') + + elif(problem_type=='regresioni'): + classifier=tf.keras.Sequential() + + classifier.add(tf.keras.layers.Dense(units=params['hiddenLayerNeurons'], activation=params['hiddenLayerActivationFunctions'][0],input_dim=x_train.shape[1]))#prvi skriveni + definisanje prethodnog-ulaznog + for i in range(params['hiddenLayers']-1):#ako postoji vise od jednog skrivenog sloja + #print(i) + classifier.add(tf.keras.layers.Dense(units=params['hiddenLayerNeurons'], activation=params['hiddenLayerActivationFunctions'][i+1]))#i-ti skriveni sloj + classifier.add(tf.keras.layers.Dense(units=1)) + + classifier.compile(loss =params["lossFunction"] , optimizer = params['optimizer'] , metrics =params['metrics']) + + history=classifier.fit(x_train, y_train, epochs = params['epochs'],batch_size=params['batchSize']) + y_pred=classifier.predict(x_test) + print(classifier.evaluate(x_test, y_test)) + + def roc_auc_score_multiclass(actual_class, pred_class, average = "macro"): + + #creating a set of all the unique classes using the actual class list + unique_class = set(actual_class) + roc_auc_dict = {} + for per_class in unique_class: + + #creating a list of all the classes except the current class + other_class = [x for x in unique_class if x != per_class] + + #marking the current class as 1 and all other classes as 0 + new_actual_class = [0 if x in other_class else 1 for x in actual_class] + new_pred_class = [0 if x in other_class else 1 for x in pred_class] + + #using the sklearn metrics method to calculate the roc_auc_score + roc_auc = roc_auc_score(new_actual_class, new_pred_class, average = average) + roc_auc_dict[per_class] = roc_auc + + return roc_auc_dict + # + # Metrike + # + + if(problem_type=="binarni-klasifikacioni"): + accuracy = float(sm.accuracy_score(y_test,y_pred)) + precision = float(sm.precision_score(y_test,y_pred)) + recall = float(sm.recall_score(y_test,y_pred)) + tn, fp, fn, tp = sm.confusion_matrix(y_test,y_pred).ravel() + specificity = float(tn / (tn+fp)) + f1 = float(sm.f1_score(y_test,y_pred)) + fpr, tpr, _ = sm.roc_curve(y_test,y_pred) + logloss = float(sm.log_loss(y_test, y_pred)) + metrics= {"accuracy" : accuracy, + "precision" : precision, + "recall" : recall, + "specificity" : specificity, + "f1" : f1, + "tn" : float(tn), + "fp" : float(fp), + "fn" : float(fn), + "tp" : float(tp), + "fpr" : fpr.tolist(), + "tpr" : tpr.tolist(), + "logloss" : logloss + } + elif(problem_type=="regresioni"): + # https://www.analyticsvidhya.com/blog/2021/05/know-the-best-evaluation-metrics-for-your-regression-model/ + mse = float(sm.mean_squared_error(y_test,y_pred)) + mae = float(sm.mean_absolute_error(y_test,y_pred)) + mape = float(sm.mean_absolute_percentage_error(y_test,y_pred)) + rmse = float(np.sqrt(sm.mean_squared_error(y_test,y_pred))) + rmsle = float(np.sqrt(sm.mean_squared_error(y_test, y_pred))) + r2 = float(sm.r2_score(y_test, y_pred)) + # n - num of observations + # k - num of independent variables + n = 40 + k = 2 + adj_r2 = float(1 - ((1-r2)*(n-1)/(n-k-1))) + metrics= {"mse" : mse, + "mae" : mae, + "mape" : mape, + "rmse" : rmse, + "rmsle" : rmsle, + "r2" : r2, + "adj_r2" : adj_r2 + } + ''' + elif(problem_type=="multi-klasifikacioni"): + + cr=sm.classification_report(y_test, y_pred) + cm=sm.confusion_matrix(y_test,y_pred) + # https://www.kaggle.com/code/nkitgupta/evaluation-metrics-for-multi-class-classification/notebook + accuracy=metrics.accuracy_score(y_test, y_pred) + macro_averaged_precision=metrics.precision_score(y_test, y_pred, average = 'macro') + micro_averaged_precision=metrics.precision_score(y_test, y_pred, average = 'micro') + macro_averaged_recall=metrics.recall_score(y_test, y_pred, average = 'macro') + micro_averaged_recall=metrics.recall_score(y_test, y_pred, average = 'micro') + macro_averaged_f1=metrics.f1_score(y_test, y_pred, average = 'macro') + micro_averaged_f1=metrics.f1_score(y_test, y_pred, average = 'micro') + roc_auc_dict=roc_auc_score_multiclass(y_test, y_pred) + ''' + +def manageH5(dataset,params,h5model): + problem_type = params["type"] + print(problem_type) + data = pd.DataFrame() + #print(data) + for col in params["inputColumns"]: + print(col) + data[col]=dataset[col] + output_column = params["columnToPredict"] + data[output_column] = dataset[output_column] + #print(data) + + ###NULL + null_value_options = params["nullValues"] + null_values_replacers = params["nullValuesReplacers"] + + if(null_value_options=='replace'): + print("replace null") # TODO + elif(null_value_options=='delete_rows'): + data=data.dropna() + elif(null_value_options=='delete_columns'): + data=data.dropna() + print(data.shape) + + # + # Brisanje kolona koje ne uticu na rezultat + # + num_rows=data.shape[0] + for col in data.columns: + if((data[col].nunique()==(num_rows)) and (data[col].dtype==np.object_)): + data.pop(col) + # + ### Enkodiranje + encoding=params["encoding"] + if(encoding=='label'): + encoder=LabelEncoder() + for col in data.columns: + if(data[col].dtype==np.object_): + data[col]=encoder.fit_transform(data[col]) + + + elif(encoding=='onehot'): + category_columns=[] + for col in data.columns: + if(data[col].dtype==np.object_): + category_columns.append(col) + data=pd.get_dummies(data, columns=category_columns, prefix=category_columns) + #print(data) + + # + # Input - output + # + x_columns = [] + for col in data.columns: + if(col!=output_column): + x_columns.append(col) + #print(x_columns) + x2 = data[x_columns] + print(x2) + print(x2.values) + x2 = data[x_columns].values + print(x2) + y2 = data[output_column].values + h5model.summary() + ann_viz(h5model, title="My neural network") + + h5model.compile(loss=params['lossFunction'], optimizer=params['optimizer'], metrics=params['metrics']) + + history=h5model.fit(x2, y2, epochs = params['epochs'],batch_size=params['batchSize']) + + y_pred2=h5model.predict(x2) + + y_pred2=np.argmax(y_pred2,axis=1) + #y_pred=h5model.predict_classes(x) + score = h5model.evaluate(x2,y_pred2, verbose=0) + print("%s: %.2f%%" % (h5model.metrics_names[1], score[1]*100)) + print(y_pred2) + print( 'done')
\ No newline at end of file diff --git a/backend/microservice/mlservice.py b/backend/microservice/mlservice.py index b2eafe9a..8f56fc3f 100644 --- a/backend/microservice/mlservice.py +++ b/backend/microservice/mlservice.py @@ -54,6 +54,38 @@ def obuka(dataunos,params,modelunos,dataunosdrugog): data[zeljenekolone[i]]=dataunos[zeljenekolone[i]] #print(data.head(10)) + ### 0.1) Povratne vrednosti statistike za front (za popunjavanje null vrednosti izabranih kolona) PART4 + datafront=data.copy() + svekolone=datafront.columns + kategorijskekolone=datafront.select_dtypes(include=['object']).columns + #print(kategorijskekolone ) + #kategorijskekolone=datacategorical.columns + #print(svekolone) + for i in range(len(svekolone)): + nazivkolone=svekolone[i] + if(nazivkolone in kategorijskekolone): + svekategorije=datafront[nazivkolone].unique() + medijana=None + srednjavrednost=None + frontreturn={'colName':nazivkolone, + 'colType':'categorical', + 'categoricalValues':svekategorije, + 'mean':medijana, + 'average':srednjavrednost + } + else: + svekategorije=None + medijana=datafront[nazivkolone].mean() + srednjavrednost=sum(datafront[nazivkolone])/len(datafront[nazivkolone]) + frontreturn={'colName':nazivkolone, + 'colType':'noncategorical', + 'categoricalValues':svekategorije, + 'mean':medijana, + 'average':srednjavrednost + } + + print(frontreturn) + #predvidetikol=input("UNETI NAZIV KOLONE ČIJU VREDNOST TREBA PREDVIDETI ") ###sta se cuva od promenjivih broj kolone ili naziv kolone??? diff --git a/frontend/angular.json b/frontend/angular.json index b1aaac3f..f9825281 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -33,7 +33,10 @@ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css" ], "scripts": [ - "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js" + "node_modules/bootstrap/dist/js/bootstrap.bundle.min.js", + "node_modules/jquery/dist/jquery.min.js", + "node_modules/popper.js/dist/popper.min.js", + "node_modules/bootstrap/dist/js/bootstrap.min.js" ] }, "configurations": { @@ -41,13 +44,13 @@ "budgets": [ { "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "maximumWarning": "2mb", + "maximumError": "4mb" }, { "type": "anyComponentStyle", - "maximumWarning": "2kb", - "maximumError": "4kb" + "maximumWarning": "10kb", + "maximumError": "15kb" } ], "fileReplacements": [ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 962905b7..c79f4ea9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -27,6 +27,7 @@ "chart.js": "^3.7.1", "csv-parser": "^3.0.0", "d3-graphviz": "^2.6.1", + "jquery": "^3.6.0", "mdb-angular-ui-kit": "^2.0.0", "ng-multiselect-dropdown": "^0.3.8", "ng-uikit-pro-standard": "^1.0.0", @@ -34,6 +35,7 @@ "ng2-search-filter": "^0.5.1", "ngx-cookie-service": "^13.1.2", "ngx-csv-parser": "^0.0.7", + "popper.js": "^1.16.1", "rxjs": "~7.5.0", "tslib": "^2.3.1", "websocket-ts": "^1.1.1", @@ -7082,6 +7084,11 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jquery": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -9018,6 +9025,16 @@ "node": ">=8" } }, + "node_modules/popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", + "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -16882,6 +16899,11 @@ } } }, + "jquery": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -18345,6 +18367,11 @@ "find-up": "^4.0.0" } }, + "popper.js": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", + "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" + }, "portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", diff --git a/frontend/package.json b/frontend/package.json index 9df48b0c..ad68e176 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,6 +30,7 @@ "chart.js": "^3.7.1", "csv-parser": "^3.0.0", "d3-graphviz": "^2.6.1", + "jquery": "^3.6.0", "mdb-angular-ui-kit": "^2.0.0", "ng-multiselect-dropdown": "^0.3.8", "ng-uikit-pro-standard": "^1.0.0", @@ -37,6 +38,7 @@ "ng2-search-filter": "^0.5.1", "ngx-cookie-service": "^13.1.2", "ngx-csv-parser": "^0.0.7", + "popper.js": "^1.16.1", "rxjs": "~7.5.0", "tslib": "^2.3.1", "websocket-ts": "^1.1.1", diff --git a/frontend/src/app/Shared.ts b/frontend/src/app/Shared.ts index 31afb1a6..86e26687 100644 --- a/frontend/src/app/Shared.ts +++ b/frontend/src/app/Shared.ts @@ -1,9 +1,32 @@ +import { ElementRef } from "@angular/core"; +import { NgbModal } from "@ng-bootstrap/ng-bootstrap"; +import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { AlertDialogComponent } from './_modals/alert-dialog/alert-dialog.component'; + class Shared { constructor( public loggedIn: boolean, public username: string = '', - public photoId: string = '1' + public photoId: string = '1', + public dialog?: MatDialog + //public alertDialog?: ElementRef ) { } + + + openDialog(title: string, message: string): void { + console.log("USAO U OPEN DIALOG 1"); + + if (this.dialog) { + console.log("USAO U OPEN DIALOG 2"); + const dialogRef = this.dialog.open(AlertDialogComponent, { + //width: '250px', + data: { title: title, message: message } + }); + dialogRef.afterClosed().subscribe(res => { + //nesto + }); + } + } } export default new Shared(false);
\ No newline at end of file diff --git a/frontend/src/app/_data/Model.ts b/frontend/src/app/_data/Model.ts index 1ad4fc6d..85b6db2b 100644 --- a/frontend/src/app/_data/Model.ts +++ b/frontend/src/app/_data/Model.ts @@ -45,22 +45,22 @@ export enum ProblemType { export enum Encoding { Label = 'label', OneHot = 'onehot', + Ordinal = 'ordinal', + Hashing = 'hashing', + Binary = 'binary', + BaseN = 'baseN' /* BackwardDifference = 'backward difference', - BaseN = 'baseN', - Binary = 'binary', CatBoost = 'cat boost', Count = 'count', GLMM = 'glmm', - Hashing = 'hashing', + Target = 'target', Helmert = 'helmert', JamesStein = 'james stein', LeaveOneOut = 'leave one out', MEstimate = 'MEstimate', - Ordinal = 'ordinal', Sum = 'sum', Polynomial = 'polynomial', - Target = 'target', WOE = 'woe', Quantile = 'quantile' */ @@ -104,7 +104,7 @@ export enum LossFunction { BinaryCrossEntropy = 'binary_crossentropy', SquaredHingeLoss = 'squared_hinge_loss', HingeLoss = 'hinge_loss', - // multi-class classiication loss functions + // multi-class classification loss functions CategoricalCrossEntropy = 'categorical_crossentropy', SparseCategoricalCrossEntropy = 'sparse_categorical_crosentropy', KLDivergence = 'kullback_leibler_divergence', diff --git a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.css b/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.css diff --git a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.html b/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.html new file mode 100644 index 00000000..dfeb4f62 --- /dev/null +++ b/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.html @@ -0,0 +1,41 @@ +<div class="row mb-4"> + <div class="col-2"> + </div> + <div class="col-3"> + <label for="name" class="col-form-label">Naziv dataseta:</label> + <input type="text" class="form-control mb-1" name="name" placeholder="Naziv..." [(ngModel)]="dataset.name"> + + <label for="desc" class="col-sm-2 col-form-label">Opis:</label> + <div> + <textarea class="form-control" name="desc" rows="3" [(ngModel)]="dataset.description"></textarea> + </div> + + <label for="checkboxIsPublic" class="form-check-label mt-3 mb-1">Želite li da dataset bude javan? + <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="dataset.isPublic" (change)="checkAccessible()" type="checkbox" + value="" id="checkboxIsPublic"> + </label> + + <label for="checkboxAccessibleByLink" class="form-check-label">Želite li da bude deljiv linkom? + <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="dataset.accessibleByLink" type="checkbox" + value="" id="checkboxAccessibleByLink"> + </label> + </div> + <div class="col-1"> + </div> + <div class="col-4 mt-4"> + + <input list=delimiterOptions placeholder="Izaberite ili ukucajte delimiter za .csv fajl" class="form-control mt-2" + [(ngModel)]="dataset.delimiter" (input)="update()"> + <datalist id=delimiterOptions> + <option *ngFor="let option of delimiterOptions">{{option}}</option> + </datalist> + + <label for="type" class="form-check-label my-5">Da li .csv ima header? + <input class="mx-3 form-check-input" type="checkbox" (input)="update()" [(ngModel)]="dataset.hasHeader" type="checkbox" + value="" id="checkboxHeader" checked> + </label> + <br> + <input id="fileInput" class="form-control" type="file" class="upload" (change)="changeListener($event)" + accept=".csv"> + </div> + </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.spec.ts b/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.spec.ts new file mode 100644 index 00000000..a9ea25b4 --- /dev/null +++ b/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AddNewDatasetComponent } from './add-new-dataset.component'; + +describe('AddNewDatasetComponent', () => { + let component: AddNewDatasetComponent; + let fixture: ComponentFixture<AddNewDatasetComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AddNewDatasetComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AddNewDatasetComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.ts b/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.ts new file mode 100644 index 00000000..fceb53cf --- /dev/null +++ b/frontend/src/app/_elements/add-new-dataset/add-new-dataset.component.ts @@ -0,0 +1,78 @@ +import { Component, EventEmitter, Output, ViewChild } from '@angular/core'; +import { NgxCsvParser, NgxCSVParserError } from 'ngx-csv-parser'; +import Dataset from 'src/app/_data/Dataset'; + +@Component({ + selector: 'app-add-new-dataset', + templateUrl: './add-new-dataset.component.html', + styleUrls: ['./add-new-dataset.component.css'] +}) +export class AddNewDatasetComponent { + + @Output() loaded = new EventEmitter<string>(); + + delimiterOptions: Array<string> = [",", ";", "\t", "razmak", "|"]; //podrazumevano "," + + //hasHeader: boolean = true; + hasInput: boolean = false; + + csvRecords: any[] = []; + files: File[] = []; + rowsNumber: number = 0; + colsNumber: number = 0; + + dataset: Dataset; //dodaj ! potencijalno + + constructor(private ngxCsvParser: NgxCsvParser) { + this.dataset = new Dataset(); + } + + //@ViewChild('fileImportInput', { static: false }) fileImportInput: any; cemu je ovo sluzilo? + + changeListener($event: any): void { + this.files = $event.srcElement.files; + if (this.files.length == 0 || this.files[0] == null) { + //console.log("NEMA FAJLA"); + //this.loaded.emit("not loaded"); + this.hasInput = false; + return; + } + else + this.hasInput = true; + + this.update(); + } + + update() { + + if (this.files.length < 1) + return; + + this.ngxCsvParser.parse(this.files[0], { header: false, delimiter: (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter == "") ? "," : this.dataset.delimiter }) + .pipe().subscribe((result) => { + + console.log('Result', result); + if (result.constructor === Array) { + this.csvRecords = result; + if (this.dataset.hasHeader) + this.rowsNumber = this.csvRecords.length - 1; + else + this.rowsNumber = this.csvRecords.length; + this.colsNumber = this.csvRecords[0].length; + + if (this.dataset.hasHeader) //kasnije dodati opciju kada nema header da korisnik rucno unosi header-e + this.dataset.header = this.csvRecords[0]; + + this.loaded.emit("loaded"); + } + }, (error: NgxCSVParserError) => { + console.log('Error', error); + }); + } + + checkAccessible() { + if (this.dataset.isPublic) + this.dataset.accessibleByLink = true; + } + +} diff --git a/frontend/src/app/_elements/annvisual/annvisual.component.html b/frontend/src/app/_elements/annvisual/annvisual.component.html index f23022de..09251398 100644 --- a/frontend/src/app/_elements/annvisual/annvisual.component.html +++ b/frontend/src/app/_elements/annvisual/annvisual.component.html @@ -1,5 +1,5 @@ -<div style="text-align: center; width: 100%;" > +<div style="text-align: center; " > <button (click)="d3()" mat-raised-button color="primary">Prikaz veštačke neuronske mreže</button> - <div id="graph" align-items-center ></div> + <div id="graph" align-items-center style="width: 12rem;"></div> </div> diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.css b/frontend/src/app/_elements/dataset-load/dataset-load.component.css index 05819702..54e0738e 100644 --- a/frontend/src/app/_elements/dataset-load/dataset-load.component.css +++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.css @@ -1,6 +1,13 @@ -#divInputs { - margin-left: 20px; +.btnType1 { + background-color: #003459; + color: white; } -#divOutputs { - margin-left: 20px; +.btnType2 { + background-color: white; + color: #003459; + border-color: #003459; +} +.selectedDatasetClass { + /*border-color: 2px solid #003459;*/ + background-color: lightblue; }
\ No newline at end of file diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.html b/frontend/src/app/_elements/dataset-load/dataset-load.component.html index 76e46092..674e5990 100644 --- a/frontend/src/app/_elements/dataset-load/dataset-load.component.html +++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.html @@ -1,44 +1,42 @@ <div> - <div class="row mb-4"> - <div class="col-2"> - </div> - <div class="col-3"> - <label for="name" class="col-form-label">Naziv dataseta:</label> - <input type="text" class="form-control mb-1" name="name" placeholder="Naziv..." [(ngModel)]="dataset.name"> - - <label for="desc" class="col-sm-2 col-form-label">Opis:</label> - <div> - <textarea class="form-control" name="desc" rows="3" [(ngModel)]="dataset.description"></textarea> - </div> + <!--Sklonjeno ucitavanje novog dataseta i sve opcije u vezi sa tim, premesteno u add-new-dataset--> - <label for="checkboxIsPublic" class="form-check-label mt-3 mb-1">Želite li da dataset bude javan? - <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="dataset.isPublic" (change)="checkAccessible()" type="checkbox" - value="" id="checkboxIsPublic"> - </label> - - <label for="checkboxAccessibleByLink" class="form-check-label">Želite li da bude deljiv linkom? - <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="dataset.accessibleByLink" type="checkbox" - value="" id="checkboxAccessibleByLink"> - </label> - </div> + <div class="col-12 d-flex my-5"> + <h2 class="">Izvor podataka:</h2> <div class="col-1"> </div> - <div class="col-4 mt-4"> - - <input list=delimiterOptions placeholder="Izaberite ili ukucajte delimiter za .csv fajl" class="form-control mt-2" - [(ngModel)]="dataset.delimiter" (input)="update()"> - <datalist id=delimiterOptions> - <option *ngFor="let option of delimiterOptions">{{option}}</option> - </datalist> + <button type="button" id="btnMyDataset" class="btn" (click)="viewMyDatasetsForm()" + [ngClass]="{'btnType1': showMyDatasets, 'btnType2': !showMyDatasets}"> + Izaberite dataset iz kolekcije + </button> + <h3 class="mt-3 mx-3">ili</h3> + <button type="button" id="btnNewDataset" class="btn" (click)="viewNewDatasetForm()" + [ngClass]="{'btnType1': !showMyDatasets, 'btnType2': showMyDatasets}"> + Dodajte novi dataset + </button> + </div> + <div class="px-5 my-2"> + <input *ngIf="showMyDatasets" type="text" class="form-control" placeholder="Pretraga" + [(ngModel)]="term"> + </div> + <div class="px-5"> + <div *ngIf="showMyDatasets" class="overflow-auto" style="max-height: 500px;"> + <ul class="list-group"> + <li class="list-group-item p-3" *ngFor="let dataset of myDatasets|filter:term" + [ngClass]="{'selectedDatasetClass': this.selectedDataset == dataset}"> + <app-item-dataset name="usersDataset" [dataset]="dataset" + (click)="selectThisDataset(dataset);"></app-item-dataset> + </li> + </ul> + </div> + </div> - <label for="type" class="form-check-label my-5">Da li .csv ima header? - <input class="mx-3 form-check-input" type="checkbox" (input)="update()" [(ngModel)]="dataset.hasHeader" type="checkbox" - value="" id="checkboxHeader" checked> - </label> - <br> - <input id="fileInput" class="form-control" type="file" class="upload" (change)="changeListener($event)" - accept=".csv"> - </div> + <app-add-new-dataset [style]="(showMyDatasets)?'display:none;visibility:hidden;':''" id="dataset" + (loaded)="datasetLoaded = true; selectedDataset = addNewDatasetComponent?.dataset; datasetFile = addNewDatasetComponent?.csvRecords; datasetHasHeader = addNewDatasetComponent?.dataset!.hasHeader"> + </app-add-new-dataset> + <div class="px-5 mt-5"> + <app-datatable [data]="datasetFile" [hasHeader]="datasetHasHeader"></app-datatable> </div> + </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.ts b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts index f9343117..ed71dc3c 100644 --- a/frontend/src/app/_elements/dataset-load/dataset-load.component.ts +++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts @@ -1,6 +1,12 @@ -import { Component, EventEmitter, Output, ViewChild } from '@angular/core'; -import { NgxCsvParser, NgxCSVParserError } from 'ngx-csv-parser'; +import { Component, OnInit, ViewChild } 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 } 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'; @Component({ selector: 'app-dataset-load', @@ -9,70 +15,77 @@ import Dataset from 'src/app/_data/Dataset'; }) export class DatasetLoadComponent { - @Output() loaded = new EventEmitter<string>(); + @Output() selectedDatasetChangeEvent = new EventEmitter<Dataset>(); - delimiterOptions: Array<string> = [",", ";", "\t", "razmak", "|"]; //podrazumevano "," + @ViewChild(AddNewDatasetComponent) addNewDatasetComponent?: AddNewDatasetComponent; + @ViewChild(AddNewDatasetComponent) datatable?: DatatableComponent; + datasetLoaded: boolean = false; + selectedDatasetLoaded: boolean = false; - //hasHeader: boolean = true; - hasInput: boolean = false; + showMyDatasets: boolean = true; + myDatasets?: Dataset[]; + existingDatasetSelected: boolean = false; + selectedDataset?: Dataset; + otherDataset?: Dataset; + otherDatasetFile?: any[]; + datasetFile?: any[]; + datasetHasHeader?: boolean = true; - csvRecords: any[] = []; - files: File[] = []; - rowsNumber: number = 0; - colsNumber: number = 0; + term: string = ""; - dataset: Dataset; //dodaj ! potencijalno - - constructor(private ngxCsvParser: NgxCsvParser) { - this.dataset = new Dataset(); + constructor(private models: ModelsService, private datasets: DatasetsService, private csv: CsvParseService) { + this.datasets.getMyDatasets().subscribe((datasets) => { + this.myDatasets = datasets; + }); } - @ViewChild('fileImportInput', { static: false }) fileImportInput: any; - - changeListener($event: any): void { - this.files = $event.srcElement.files; - if (this.files.length == 0 || this.files[0] == null) { - //console.log("NEMA FAJLA"); - //this.loaded.emit("not loaded"); - this.hasInput = false; - return; - } - else - this.hasInput = true; - - this.update(); + viewMyDatasetsForm() { + this.showMyDatasets = true; + this.resetSelectedDataset(); + //this.resetCbsAndRbs(); //TREBA DA SE DESI + } + viewNewDatasetForm() { + this.showMyDatasets = false; + this.resetSelectedDataset(); + //this.resetCbsAndRbs(); //TREBA DA SE DESI } - update() { - - if (this.files.length < 1) - return; - - this.ngxCsvParser.parse(this.files[0], { header: false, delimiter: (this.dataset.delimiter == "razmak") ? " " : (this.dataset.delimiter == "") ? "," : this.dataset.delimiter }) - .pipe().subscribe((result) => { - - console.log('Result', result); - if (result.constructor === Array) { - this.csvRecords = result; - if (this.dataset.hasHeader) - this.rowsNumber = this.csvRecords.length - 1; + selectThisDataset(dataset: Dataset) { + this.selectedDataset = dataset; + this.selectedDatasetLoaded = false; + this.existingDatasetSelected = true; + this.datasetHasHeader = this.selectedDataset.hasHeader; + + this.datasets.getDatasetFile(dataset.fileId).subscribe((file: string | undefined) => { + if (file) { + this.datasetFile = this.csv.csvToArray(file, (dataset.delimiter == "razmak") ? " " : (dataset.delimiter == "") ? "," : dataset.delimiter); + /*for (let i = this.datasetFile.length - 1; i >= 0; i--) { //moguce da je vise redova na kraju fajla prazno i sl. + if (this.datasetFile[i].length != this.datasetFile[0].length) + this.datasetFile[i].pop(); else - this.rowsNumber = this.csvRecords.length; - this.colsNumber = this.csvRecords[0].length; + break; //nema potrebe dalje + }*/ + //console.log(this.datasetFile); + //this.resetCbsAndRbs(); //TREBA DA SE DESI + //this.refreshThreeNullValueRadioOptions(); //TREBA DA SE DESI + this.selectedDatasetLoaded = true; + //this.scrollToNextForm(); + } + }); + } - if (this.dataset.hasHeader) //kasnije dodati opciju kada nema header da korisnik rucno unosi header-e - this.dataset.header = this.csvRecords[0]; + resetSelectedDataset(): boolean { + const temp = this.selectedDataset; + this.selectedDataset = this.otherDataset; + this.otherDataset = temp; + const tempFile = this.datasetFile; + this.datasetFile = this.otherDatasetFile; + this.otherDatasetFile = tempFile; - this.loaded.emit("loaded"); - } - }, (error: NgxCSVParserError) => { - console.log('Error', error); - }); - } + this.selectedDatasetChangeEvent.emit(this.selectedDataset); - checkAccessible() { - if (this.dataset.isPublic) - this.dataset.accessibleByLink = true; + return true; } + } diff --git a/frontend/src/app/_elements/item-model/item-model.component.html b/frontend/src/app/_elements/item-model/item-model.component.html index 9466da01..c8c1a36d 100644 --- a/frontend/src/app/_elements/item-model/item-model.component.html +++ b/frontend/src/app/_elements/item-model/item-model.component.html @@ -6,9 +6,9 @@ <div class="card-body overflow-hidden"> <p class="card-text"> {{"Opis: "+ model.description}}<br> - {{"Datum kreiranja:" + model.dateCreated}}<br> - {{"Poslednje ažuriranje:" + model.lastUpdated}}<br> + {{"Datum kreiranja: " + model.dateCreated}}<br> + {{"Poslednje ažuriranje: " + model.lastUpdated}}<br> </p> - + <app-annvisual class="align-items-center" [model]="model" style="width: 12rem"></app-annvisual> </div> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/item-predictor/item-predictor.component.ts b/frontend/src/app/_elements/item-predictor/item-predictor.component.ts index b6b5c9db..246032e0 100644 --- a/frontend/src/app/_elements/item-predictor/item-predictor.component.ts +++ b/frontend/src/app/_elements/item-predictor/item-predictor.component.ts @@ -17,8 +17,7 @@ export class ItemPredictorComponent implements OnInit { } openPredictor() { - this.router.navigate(['predict/'+ '6244958a26cf2385bc29ba2c']); - //this.router.navigate(['predict'+this.predictor._id]); + this.router.navigate(['predict/'+ this.predictor._id]); } } diff --git a/frontend/src/app/_elements/navbar/navbar.component.ts b/frontend/src/app/_elements/navbar/navbar.component.ts index 2e4bde91..368508ed 100644 --- a/frontend/src/app/_elements/navbar/navbar.component.ts +++ b/frontend/src/app/_elements/navbar/navbar.component.ts @@ -3,6 +3,7 @@ import { Location } from '@angular/common'; import { AuthService } from '../../_services/auth.service'; import shared from 'src/app/Shared'; import { UserInfoService } from 'src/app/_services/user-info.service'; +import { MatDialog } from '@angular/material/dialog'; @Component({ selector: 'app-navbar', @@ -14,7 +15,8 @@ export class NavbarComponent implements OnInit { currentUrl: string; shared = shared; - constructor(public location: Location, private auth: AuthService, private userInfoService: UserInfoService) { + constructor(public location: Location, private auth: AuthService, private userInfoService: UserInfoService, private matDialog: MatDialog) { + shared.dialog = matDialog; this.currentUrl = this.location.path(); this.location.onUrlChange(() => { this.currentUrl = this.location.path(); diff --git a/frontend/src/app/_modals/alert-dialog/alert-dialog.component.css b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.css diff --git a/frontend/src/app/_modals/alert-dialog/alert-dialog.component.html b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.html new file mode 100644 index 00000000..82365193 --- /dev/null +++ b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.html @@ -0,0 +1,7 @@ +<h2 mat-dialog-title class="text-muted">{{data.title}}</h2> +<div mat-dialog-content class="mt-4" style="color: rgb(81, 76, 76);"> + {{data.message}} +</div> +<div mat-dialog-actions class="d-flex justify-content-center mt-4"> + <button mat-button cdkFocusInitial (click)="onOkClick()" style="background-color: lightgray;">OK</button> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_modals/alert-dialog/alert-dialog.component.spec.ts b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.spec.ts new file mode 100644 index 00000000..a93fc493 --- /dev/null +++ b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AlertDialogComponent } from './alert-dialog.component'; + +describe('AlertDialogComponent', () => { + let component: AlertDialogComponent; + let fixture: ComponentFixture<AlertDialogComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AlertDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AlertDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_modals/alert-dialog/alert-dialog.component.ts b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.ts new file mode 100644 index 00000000..e15f3c6f --- /dev/null +++ b/frontend/src/app/_modals/alert-dialog/alert-dialog.component.ts @@ -0,0 +1,28 @@ +import { Component, OnInit } from '@angular/core'; +import { Inject} from '@angular/core'; +import { MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material/dialog'; + +interface DialogData { + title: string; + message: string; +} + +@Component({ + selector: 'app-alert-dialog', + templateUrl: './alert-dialog.component.html', + styleUrls: ['./alert-dialog.component.css'] +}) +export class AlertDialogComponent { + + constructor( + public dialogRef: MatDialogRef<AlertDialogComponent>, + @Inject(MAT_DIALOG_DATA) public data: DialogData, + //public dialog: MatDialog + ) {} + + onOkClick(): void { + this.dialogRef.close(); + } + + +} diff --git a/frontend/src/app/_modals/login-modal/login-modal.component.html b/frontend/src/app/_modals/login-modal/login-modal.component.html index d7836848..03048155 100644 --- a/frontend/src/app/_modals/login-modal/login-modal.component.html +++ b/frontend/src/app/_modals/login-modal/login-modal.component.html @@ -3,7 +3,7 @@ <div class="modal-dialog modal-dialog-centered"> <div class="modal-content"> <div class="modal-header" style="background-color: #003459;"> - <button id="closeButton" type="button" class="btn-close" style="background-color:white;" data-bs-dismiss="modal" aria-label="Close" (click)="resetData()"></button> + <button #closeButton type="button" class="btn-close" style="background-color:white;" data-bs-dismiss="modal" aria-label="Close" (click)="resetData()"></button> </div> <div class="modal-body px-5" style="color:#003459"> <h1 class="text-center mt-2 mb-4">Prijavite se</h1> 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 c86c269a..e1535a25 100644 --- a/frontend/src/app/_modals/login-modal/login-modal.component.ts +++ b/frontend/src/app/_modals/login-modal/login-modal.component.ts @@ -4,6 +4,7 @@ import { CookieService } from 'ngx-cookie-service'; import { AuthService } from 'src/app/_services/auth.service'; import { UserInfoService } from 'src/app/_services/user-info.service'; import shared from '../../Shared'; +import {AfterViewInit, ElementRef} from '@angular/core'; @Component({ selector: 'app-login-modal', @@ -12,6 +13,8 @@ import shared from '../../Shared'; }) export class LoginModalComponent implements OnInit { + @ViewChild('closeButton') closeButton?: ElementRef; + username: string = ''; password: string = ''; @@ -38,7 +41,7 @@ export class LoginModalComponent implements OnInit { } else { this.authService.authenticate(response); - (<HTMLSelectElement>document.getElementById('closeButton')).click(); + (<HTMLSelectElement>this.closeButton?.nativeElement).click(); this.userInfoService.getUserInfo().subscribe((response) => { shared.photoId = response.photoId; }); 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 13ef7eba..05888589 100644 --- a/frontend/src/app/_modals/register-modal/register-modal.component.ts +++ b/frontend/src/app/_modals/register-modal/register-modal.component.ts @@ -1,6 +1,9 @@ import { Component, OnInit } from '@angular/core'; import { AuthService } from 'src/app/_services/auth.service'; import User from 'src/app/_data/User'; +import { DOCUMENT } from '@angular/common'; +import { Inject } from '@angular/core'; +import shared from 'src/app/Shared'; @Component({ selector: 'app-register-modal', @@ -29,8 +32,11 @@ export class RegisterModalComponent implements OnInit { pattEmail: RegExp = /^[a-zA-Z0-9]+([\.\-\+][a-zA-Z0-9]+)*\@([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}$/; pattPassword: RegExp = /.{6,30}$/; + shared = shared; + constructor( - private authService: AuthService + private authService: AuthService, + @Inject(DOCUMENT) document: Document ) { } ngOnInit(): void { @@ -149,11 +155,11 @@ export class RegisterModalComponent implements OnInit { }, (error) => console.warn(error)); } else if (response == 'Email Already Exists') { - alert('Nalog sa unetim email-om već postoji!'); + shared.openDialog("Greška!", "Nalog sa unetim email-om već postoji!"); (<HTMLSelectElement>document.getElementById('email')).focus(); } else if (response == 'Username Already Exists') { - alert('Nalog sa unetim korisničkim imenom već postoji!'); + shared.openDialog("Greška!", "Nalog sa unetim korisničkim imenom već postoji!"); (<HTMLSelectElement>document.getElementById('username-register')).focus(); } } diff --git a/frontend/src/app/_pages/add-model/add-model.component.css b/frontend/src/app/_pages/add-model/add-model.component.css index 6d961287..7f05af0f 100644 --- a/frontend/src/app/_pages/add-model/add-model.component.css +++ b/frontend/src/app/_pages/add-model/add-model.component.css @@ -32,4 +32,11 @@ } ul li:hover { background-color: lightblue; -}
\ No newline at end of file +} + +#divInputs { + margin-left: 20px; +} +#divOutputs { + margin-left: 20px; +} diff --git a/frontend/src/app/_pages/add-model/add-model.component.html b/frontend/src/app/_pages/add-model/add-model.component.html index 3d5fd7b1..179e9aea 100644 --- a/frontend/src/app/_pages/add-model/add-model.component.html +++ b/frontend/src/app/_pages/add-model/add-model.component.html @@ -25,42 +25,8 @@ <div class="py-3 pr-5 justify-content-center"> - <div class="col-12 d-flex my-5"> - <h2 class="">Izvor podataka:</h2> - <div class="col-1"> - </div> - <button type="button" id="btnMyDataset" class="btn" (click)="viewMyDatasetsForm()" - [ngClass]="{'btnType1': showMyDatasets, 'btnType2': !showMyDatasets}"> - Izaberite dataset iz kolekcije - </button> - <h3 class="mt-3 mx-3">ili</h3> - <button type="button" id="btnNewDataset" class="btn" (click)="viewNewDatasetForm()" - [ngClass]="{'btnType1': !showMyDatasets, 'btnType2': showMyDatasets}"> - Dodajte novi dataset - </button> - </div> - <div class="px-5 my-2"> - <input *ngIf="showMyDatasets" type="text" class="form-control" placeholder="Pretraga" - [(ngModel)]="term"> - </div> - <div class="px-5"> - <div *ngIf="showMyDatasets" class="overflow-auto" style="max-height: 500px;"> - <ul class="list-group"> - <li class="list-group-item p-3" *ngFor="let dataset of myDatasets|filter:term" - [ngClass]="{'selectedDatasetClass': this.selectedDataset == dataset}"> - <app-item-dataset name="usersDataset" [dataset]="dataset" - (click)="selectThisDataset(dataset);"></app-item-dataset> - </li> - </ul> - </div> - </div> + <app-dataset-load (selectedDatasetChangeEvent)="datasetHasChanged($event)"></app-dataset-load> - <app-dataset-load [style]="(showMyDatasets)?'display:none;visibility:hidden;':''" id="dataset" - (loaded)="scrollToNextForm(); datasetLoaded = true; selectedDataset = datasetLoadComponent?.dataset; datasetFile = datasetLoadComponent?.csvRecords; datasetHasHeader = datasetLoadComponent?.dataset!.hasHeader"> - </app-dataset-load> - <div class="px-5 mt-5"> - <app-datatable [data]="datasetFile" [hasHeader]="datasetHasHeader"></app-datatable> - </div> </div> <span id="selectInAndOuts"></span> <div diff --git a/frontend/src/app/_pages/add-model/add-model.component.ts b/frontend/src/app/_pages/add-model/add-model.component.ts index d47b24e6..ba8f7d01 100644 --- a/frontend/src/app/_pages/add-model/add-model.component.ts +++ b/frontend/src/app/_pages/add-model/add-model.component.ts @@ -5,9 +5,7 @@ import { DatasetLoadComponent } from 'src/app/_elements/dataset-load/dataset-loa import { ModelsService } from 'src/app/_services/models.service'; import shared from 'src/app/Shared'; import Dataset from 'src/app/_data/Dataset'; -import { DatatableComponent } from 'src/app/_elements/datatable/datatable.component'; import { DatasetsService } from 'src/app/_services/datasets.service'; -import { NgxCsvParser } from 'ngx-csv-parser'; import { CsvParseService } from 'src/app/_services/csv-parse.service'; @@ -18,11 +16,6 @@ import { CsvParseService } from 'src/app/_services/csv-parse.service'; }) export class AddModelComponent implements OnInit { - @ViewChild(DatasetLoadComponent) datasetLoadComponent?: DatasetLoadComponent; - @ViewChild(DatatableComponent) datatable?: DatatableComponent; - datasetLoaded: boolean = false; - selectedDatasetLoaded: boolean = false; - newModel: Model; ProblemType = ProblemType; @@ -71,8 +64,13 @@ export class AddModelComponent implements OnInit { (<HTMLInputElement>document.getElementById("btnMyDataset")).focus(); } + datasetHasChanged(selectedDataset: Dataset) { + this.selectedDataset = selectedDataset; + this.resetCbsAndRbs(); + this.refreshThreeNullValueRadioOptions(); + } - viewMyDatasetsForm() { + /*viewMyDatasetsForm() { this.showMyDatasets = true; this.resetSelectedDataset(); //this.datasetLoaded = false; @@ -82,7 +80,7 @@ export class AddModelComponent implements OnInit { this.showMyDatasets = false; this.resetSelectedDataset(); this.resetCbsAndRbs(); - } + }*/ addModel() { if (!this.showMyDatasets) @@ -174,7 +172,7 @@ export class AddModelComponent implements OnInit { this.models.addModel(this.newModel).subscribe((response) => { callback(response); }, (error) => { - alert("Model sa unetim nazivom već postoji u Vašoj kolekciji.\nPromenite naziv modela i nastavite sa kreiranim datasetom."); + shared.openDialog("Neuspeo pokušaj!", "Model sa unetim nazivom već postoji u Vašoj kolekciji. Promenite naziv modela i nastavite sa kreiranim datasetom."); }); } } @@ -217,47 +215,47 @@ export class AddModelComponent implements OnInit { return false; } else if (this.newModel.inputColumns.length == 0) { - alert("Molimo Vas da izaberete ulaznu kolonu/kolone za mrežu."); + shared.openDialog("Neuspeo pokušaj!", "Molimo Vas da izaberete ulaznu kolonu/kolone za mrežu."); return false; } else if (this.newModel.columnToPredict == '') { - alert("Molimo Vas da izaberete izlaznu kolonu za mrežu."); + shared.openDialog("Neuspeo pokušaj!", "Molimo Vas da izaberete izlaznu kolonu za mrežu."); return false; } for (let i = 0; i < this.newModel.inputColumns.length; i++) { if (this.newModel.inputColumns[i] == this.newModel.columnToPredict) { let colName = this.newModel.columnToPredict; - alert("Izabrali ste istu kolonu (" + colName + ") kao ulaznu i izlaznu iz mreže. Korigujte izbor."); + shared.openDialog("Neuspeo pokušaj!", "Izabrali ste istu kolonu (" + colName + ") kao ulaznu i izlaznu iz mreže. Korigujte izbor."); return false; } } return true;*/ } - selectThisDataset(dataset: Dataset) { + /*selectThisDataset(dataset: Dataset) { this.selectedDataset = dataset; - this.selectedDatasetLoaded = false; + //this.selectedDatasetLoaded = false; this.existingDatasetSelected = true; this.datasetHasHeader = this.selectedDataset.hasHeader; this.datasets.getDatasetFile(dataset.fileId).subscribe((file: string | undefined) => { if (file) { this.datasetFile = this.csv.csvToArray(file, (dataset.delimiter == "razmak") ? " " : (dataset.delimiter == "") ? "," : dataset.delimiter); - /*for (let i = this.datasetFile.length - 1; i >= 0; i--) { //moguce da je vise redova na kraju fajla prazno i sl. - if (this.datasetFile[i].length != this.datasetFile[0].length) - this.datasetFile[i].pop(); - else - break; //nema potrebe dalje - }*/ + //for (let i = this.datasetFile.length - 1; i >= 0; i--) { //moguce da je vise redova na kraju fajla prazno i sl. + //if (this.datasetFile[i].length != this.datasetFile[0].length) + //this.datasetFile[i].pop(); + //else + // break; //nema potrebe dalje + //} //console.log(this.datasetFile); this.resetCbsAndRbs(); this.refreshThreeNullValueRadioOptions(); - this.selectedDatasetLoaded = true; + //this.selectedDatasetLoaded = true; this.scrollToNextForm(); } }); //this.datasetHasHeader = false; - } + }*/ scrollToNextForm() { (<HTMLSelectElement>document.getElementById("selectInAndOuts")).scrollIntoView({ @@ -267,7 +265,7 @@ export class AddModelComponent implements OnInit { }); } - resetSelectedDataset(): boolean { + /*resetSelectedDataset(): boolean { const temp = this.selectedDataset; this.selectedDataset = this.otherDataset; this.otherDataset = temp; @@ -275,7 +273,7 @@ export class AddModelComponent implements OnInit { this.datasetFile = this.otherDatasetFile; this.otherDatasetFile = tempFile; return true; - } + }*/ resetCbsAndRbs(): boolean { this.uncheckRbs(); this.checkAllCbs(); @@ -345,7 +343,7 @@ export class AddModelComponent implements OnInit { let colIndex = this.findColIndexByName(colName); let sumOfNulls = 0; - let startValue = (this.datasetLoadComponent?.dataset.hasHeader) ? 1 : 0; + let startValue = (this.selectedDataset!.hasHeader) ? 1 : 0; for (let i = startValue; i < this.datasetFile.length; i++) { if (this.datasetFile[i][colIndex] == "" || this.datasetFile[i][colIndex] == undefined) ++sumOfNulls; @@ -360,7 +358,7 @@ export class AddModelComponent implements OnInit { let sum = 0; let n = 0; - let startValue = (this.datasetLoadComponent?.dataset.hasHeader) ? 1 : 0; + let startValue = (this.selectedDataset!.hasHeader) ? 1 : 0; for (let i = startValue; i < this.datasetFile.length; i++) if (this.datasetFile[i][colIndex] != '') { sum += Number(this.datasetFile[i][colIndex]); @@ -467,9 +465,13 @@ export class AddModelComponent implements OnInit { arrayColumn = (arr: any[][], n: number) => [...this.dropEmptyString(new Set(arr.map(x => x[n])))]; - dropEmptyString(set: Set<string>): Set<string> { + 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; } diff --git a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.html b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.html index a4ab6e2c..27e06884 100644 --- a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.html +++ b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.html @@ -5,7 +5,9 @@ <div class="row mt-3 mb-2 d-flex justify-content-center"> <div class="col-sm-6" style="margin-bottom: 10px;"> + <p class="glyphicon glyphicon-search"></p> <input type="text" class="form-control" placeholder="Pretraga" [(ngModel)]="term"> + </div> <div class="row"> @@ -14,7 +16,7 @@ <div class="card-body"> <h3 class="card-title"><b>{{predictor.name}}</b></h3> <p class="card-text">{{predictor.description}}</p> - <a class="btn btn-primary" (click)="openPredictor(predictor._id)">Otvori</a> + <a class="btn btn-primary" (click)="openPredictor(predictor._id)">Iskoristi</a> </div> <div class="card-footer text-muted"> Kreirao: {{predictor.username}} <br> diff --git a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.ts b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.ts index 4f96fc36..891b3cab 100644 --- a/frontend/src/app/_pages/browse-predictors/browse-predictors.component.ts +++ b/frontend/src/app/_pages/browse-predictors/browse-predictors.component.ts @@ -20,7 +20,7 @@ export class BrowsePredictorsComponent implements OnInit { ngOnInit(): void { } openPredictor(id:string):void{ - this.router.navigateByUrl('/predict?id='+id); + this.router.navigate(['predict/'+id]); }; } diff --git a/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts b/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts index b75decf2..fc146046 100644 --- a/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts +++ b/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts @@ -4,6 +4,7 @@ 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', @@ -12,6 +13,7 @@ import { CookieService } from 'ngx-cookie-service'; }) export class FilterDatasetsComponent implements OnInit { + shared = shared; publicDatasets?: Dataset[]; term: string = ""; constructor(private datasets: DatasetsService,private router:Router, private cookie: CookieService) { @@ -37,11 +39,9 @@ export class FilterDatasetsComponent implements OnInit { if(name!=null && name!="") this.datasets.addDataset(newDataset).subscribe((response:string)=>{ console.log(response); - alert("Uspenso ste dodali dataset sa imenom "+newDataset.name); + shared.openDialog("Obaveštenje", "Uspešno ste dodali dataset sa nazivom " + newDataset.name); },(error)=>{ - alert("Vec imate dataset sa istim imenom molim vas unesite drugo ime"); - - + 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-datasets/my-datasets.component.html b/frontend/src/app/_pages/my-datasets/my-datasets.component.html index 623b9ac8..2e17201d 100644 --- a/frontend/src/app/_pages/my-datasets/my-datasets.component.html +++ b/frontend/src/app/_pages/my-datasets/my-datasets.component.html @@ -1,5 +1,37 @@ -<ul class="list-group my-2"> - <li class="list-group-item" *ngFor="let dataset of myDatasets"> - <app-item-dataset [dataset]="dataset"></app-item-dataset> - </li> -</ul>
\ No newline at end of file +<div id="wrapper"> + <div id="container" class="container p-5" style="background-color: white; 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 dataset of myDatasets"> + <app-item-dataset [dataset]="dataset"></app-item-dataset> + + <div class="panel-footer row"><!-- panel-footer --> + <div class="col-xs-6 text-center"> + <div> + <button type="button" class="btn btn-default btn-lg" mat-raised-button color="primary" (click)="deleteThisDataset(dataset)">Obriši + <span class="glyphicon glyphicon-chevron-right"></span> + </button> + </div> + </div> + </div><!-- end panel-footer --> + + + + </div> + </div> + <div class="text-center" *ngIf="this.myDatasets.length == 0" > + <h2>Nema rezultata</h2> + </div> + </div> + + </div> + + + + + + </div> diff --git a/frontend/src/app/_pages/my-datasets/my-datasets.component.ts b/frontend/src/app/_pages/my-datasets/my-datasets.component.ts index 13b0c47b..eb5e32f8 100644 --- a/frontend/src/app/_pages/my-datasets/my-datasets.component.ts +++ b/frontend/src/app/_pages/my-datasets/my-datasets.component.ts @@ -1,5 +1,9 @@ import { Component, OnInit } from '@angular/core'; +import {Router} from '@angular/router'; +import { DatasetsService } from 'src/app/_services/datasets.service'; import Dataset from 'src/app/_data/Dataset'; +import { JwtHelperService } from '@auth0/angular-jwt'; +import { CookieService } from 'ngx-cookie-service'; @Component({ selector: 'app-my-datasets', @@ -7,18 +11,47 @@ import Dataset from 'src/app/_data/Dataset'; styleUrls: ['./my-datasets.component.css'] }) export class MyDatasetsComponent implements OnInit { + myDatasets: Dataset[] = []; - myDatasets?: Dataset[]; + constructor(private datasetsS : DatasetsService) { - constructor() { - this.myDatasets = [ - new Dataset('Titanik', 'Opis titanik', ['K1', 'K2', 'K3', 'Ime', 'Preziveli']), - new Dataset('Neki drugi set', 'opis', ['a', 'b', 'c']), - new Dataset('Treci set', 'opis', ['a', 'b', 'c']) - ]; - } + + + } ngOnInit(): void { + this.datasetsS.getMyDatasets(); + + } +/* + editModel(): void{ + this.modelsS.editModel().subscribe(m => { + this.myModel = m; + + }) + } +*/ + +deleteThisDataset(dataset: Dataset): void{ + console.log("OK"); + this.datasetsS.deleteDataset(dataset).subscribe((response) => { + console.log("OBRISANO JE", response); + //na kraju uspesnog + this.getAllMyDatasets(); + }, (error) =>{ + if (error.error == "Dataset with name = {name} deleted") { + alert("Greška pri brisanju dataseta!"); + } + }); + +} + + getAllMyDatasets(): void{ + this.datasetsS.getMyDatasets().subscribe(m => { + + this.myDatasets = m; + console.log(this.myDatasets); + }); } } diff --git a/frontend/src/app/_pages/my-models/my-models.component.html b/frontend/src/app/_pages/my-models/my-models.component.html index e2533d89..4aebc1f2 100644 --- a/frontend/src/app/_pages/my-models/my-models.component.html +++ b/frontend/src/app/_pages/my-models/my-models.component.html @@ -8,12 +8,22 @@ <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> - <app-annvisual align-items-center [model]="model" style="width: 100%;"></app-annvisual> - <div style="width: 25%; margin: auto;"> - <button mat-raised-button color="primary" (click)="deleteThisModel(model)" style="margin-top: 3px; width: 100%;">Obriši</button> - - <button mat-raised-button color="primary" (click)="deleteThisModel(model)" style="margin-top: 3px; width: 100%;">Koristi</button> - </div> + + <div class="panel-footer row"><!-- panel-footer --> + <div class="col-xs-6 text-center"> + <div> + <button type="button" class="btn btn-default btn-lg" (click)="deleteThisModel(model)" mat-raised-button color="primary">Koristi + <span class="glyphicon glyphicon-search"></span> + </button> + <button type="button" class="btn btn-default btn-lg" mat-raised-button color="primary" (click)="deleteThisModel(model)">Obriši + <span class="glyphicon glyphicon-chevron-right"></span> + </button> + </div> + </div> + </div><!-- end panel-footer --> + + + </div> </div> <div class="text-center" *ngIf="this.myModels.length == 0" > diff --git a/frontend/src/app/_pages/my-models/my-models.component.ts b/frontend/src/app/_pages/my-models/my-models.component.ts index bd6b0a2b..6086b1b1 100644 --- a/frontend/src/app/_pages/my-models/my-models.component.ts +++ b/frontend/src/app/_pages/my-models/my-models.component.ts @@ -1,4 +1,5 @@ import { Component, OnInit } from '@angular/core'; +import shared from 'src/app/Shared'; import Model from 'src/app/_data/Model'; import { ModelsService } from 'src/app/_services/models.service'; @@ -38,7 +39,7 @@ deleteThisModel(model: Model): void{ this.getAllMyModels(); }, (error) =>{ if (error.error == "Model with name = {name} deleted") { - alert("Greška pri brisanju modela!"); + shared.openDialog("Obaveštenje", "Greška prilikom brisanja modela."); } }); diff --git a/frontend/src/app/_pages/my-predictors/my-predictors.component.html b/frontend/src/app/_pages/my-predictors/my-predictors.component.html index d739f561..3746d35e 100644 --- a/frontend/src/app/_pages/my-predictors/my-predictors.component.html +++ b/frontend/src/app/_pages/my-predictors/my-predictors.component.html @@ -7,7 +7,7 @@ <app-item-predictor [predictor]="predictor"></app-item-predictor> </div> <div> - <button (click)="delete()" mat-raised-button color="warn" style="min-width: 15rem;float: right" ><mat-icon>delete</mat-icon></button> + <button (click)="delete(predictor)" mat-raised-button color="warn" style="min-width: 15rem;float: right" ><mat-icon>delete</mat-icon></button> </div> </div> diff --git a/frontend/src/app/_pages/my-predictors/my-predictors.component.ts b/frontend/src/app/_pages/my-predictors/my-predictors.component.ts index 58daa44f..13cfdab2 100644 --- a/frontend/src/app/_pages/my-predictors/my-predictors.component.ts +++ b/frontend/src/app/_pages/my-predictors/my-predictors.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from '@angular/core'; import Predictor from 'src/app/_data/Predictor'; +import { PredictorsService } from 'src/app/_services/predictors.service'; @Component({ selector: 'app-my-predictors', @@ -7,22 +8,38 @@ import Predictor from 'src/app/_data/Predictor'; styleUrls: ['./my-predictors.component.css'] }) export class MyPredictorsComponent implements OnInit { - predictors: Predictor[]; - constructor() { - this.predictors = [ - new Predictor('Titanik', 'Opis titanik', ['K1', 'K2', 'K3', 'Ime', 'Preziveli'],'Preziveli'), - new Predictor('Neki drugi set', 'opis', ['a', 'b', 'c'],'c'), - new Predictor('Preživeli', 'Za uneto ime osobe, predvidja da li je ta osoba preživela ili ne.', ['Ime'], 'OsobaJePreživela'), - new Predictor('Drugi model', 'Lorem ipsum dolor sir amet', ['kruska'], 'jagoda')]; + predictors: Predictor[] = []; + constructor(private predictorsS : PredictorsService) { } ngOnInit(): void { + this.getAllMyPredictors(); + } - delete(){ - confirm("IZABRANI MODEL ĆE BITI IZBRISAN") + delete(predictor: Predictor){ + if(window.confirm("IZABRANI MODEL ĆE BITI IZBRISAN")) + { + this.predictorsS.deletePredictor(predictor).subscribe((response) => { + console.log("OBRISANOOO JEE", response); + //na kraju uspesnog + this.getAllMyPredictors(); + }, (error) =>{ + if (error.error == "Predictor with name = {name} deleted") { + alert("Greška pri brisanju modela!"); + } + }); + } + } - + + getAllMyPredictors(): void{ + this.predictorsS.getMyPredictors().subscribe(m => { + + this.predictors = m; + console.log(this.predictors); + }); + } } diff --git a/frontend/src/app/_pages/predict/predict.component.css b/frontend/src/app/_pages/predict/predict.component.css index e69de29b..dab059a5 100644 --- a/frontend/src/app/_pages/predict/predict.component.css +++ b/frontend/src/app/_pages/predict/predict.component.css @@ -0,0 +1,3 @@ +#wrapper { + color: #003459; +}
\ No newline at end of file diff --git a/frontend/src/app/_pages/predict/predict.component.html b/frontend/src/app/_pages/predict/predict.component.html index fe17c96d..13afa8e4 100644 --- a/frontend/src/app/_pages/predict/predict.component.html +++ b/frontend/src/app/_pages/predict/predict.component.html @@ -18,8 +18,25 @@ <div> <label for="output" class="col-sm-5 col-form-label">Opis prediktora: <b>{{predictor.description}}</b></label> </div> + + + </div> + <br> + <label for="type" class="form-check-label" ><b>Informacije o prediktoru</b></label> + <div class="col-5 mt-2"> + <label for="type" class="form-check-label" >Prediktor {{predictor.isPublic?"je":"nije"}} javni.</label> + </div> + <div class="col-5 mt-2"> + <label for="type" class="form-check-label" >Prediktor {{predictor.accessibleByLink?"je":"nije"}} dostupan za deljenje.</label> + </div> + <br> + <div class="col-2"> + <label for="dateCreated" class="col-form-label">Datum:</label> + <input type="text" class="form-control-plaintext" id="dateCreated" placeholder="--/--/--" + value="{{predictor.dateCreated | date: 'dd/MM/yyyy'}}" readonly> </div> + <br> <div > <!--input --> @@ -28,44 +45,20 @@ <div *ngIf="predictor" class="form-group row mt-3 mb-2 d-flex justify-content-left"> <div *ngFor="let input of predictor.inputs; let i = index"> <label for="{{input}}" class="col-sm-2 col-form-label"><b>{{input}}</b></label> - <input name="{{input}}" type="text" [(ngModel)]="inputs[i]" > + <input name="{{input}}" type="text" [(ngModel)]="inputs[i].value" > </div> </div> </div> - + <br> </div> <div> <label for="output" class="col-sm-2 col-form-label">Izlaz: <b>{{predictor.output}}</b></label> </div> - <br> - <br> - <br> - <br> - <br> - - <div class="col-5 mt-2"> - <label for="type" class="form-check-label">Da li je prediktor javan?</label> - <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="predictor.isPublic" - type="checkbox" value="" > - </div> - <div class="col-5 mt-2"> - <label for="type" class="form-check-label">Da li je dostupan za deljenje?</label> - <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="predictor.accessibleByLink" - type="checkbox" value="" > - </div> - <br> - <div class="col-2"> - <label for="dateCreated" class="col-form-label">Datum:</label> - <input type="text" class="form-control-plaintext" id="dateCreated" placeholder="--/--/--" - value="{{predictor.dateCreated | date: 'dd/MM/yyyy'}}" readonly> - </div> - - <br><br> <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;" diff --git a/frontend/src/app/_pages/predict/predict.component.ts b/frontend/src/app/_pages/predict/predict.component.ts index 3f431fff..1c1c7425 100644 --- a/frontend/src/app/_pages/predict/predict.component.ts +++ b/frontend/src/app/_pages/predict/predict.component.ts @@ -2,6 +2,7 @@ import { Component, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import Predictor from 'src/app/_data/Predictor'; import { PredictorsService } from 'src/app/_services/predictors.service'; +import shared from 'src/app/Shared'; @Component({ selector: 'app-predict', @@ -10,7 +11,8 @@ import { PredictorsService } from 'src/app/_services/predictors.service'; }) export class PredictComponent implements OnInit { - inputs : String[] = []; + inputs : Column[] = []; + predictor:Predictor; constructor(private predictS : PredictorsService, private route: ActivatedRoute) { @@ -22,6 +24,7 @@ export class PredictComponent implements OnInit { this.predictS.getPredictor(url["id"]).subscribe(p => { this.predictor = p; + this.predictor.inputs.forEach((p,index)=> this.inputs[index] = new Column(p, "")); console.log(this.predictor); }) }); @@ -29,9 +32,16 @@ export class PredictComponent implements OnInit { usePredictor(): void{ this.predictS.usePredictor(this.predictor, this.inputs).subscribe(p => { - - alert("Uspesno ste poslali preditor!"); + shared.openDialog("Obaveštenje", "Prediktor je uspešno poslat na probu."); //pisalo je "na treniranje" ?? }) console.log(this.inputs); } } + + +export class Column { + constructor( + public name : string, + public value : (number | string)){ + } +}
\ No newline at end of file diff --git a/frontend/src/app/_pages/profile/profile.component.html b/frontend/src/app/_pages/profile/profile.component.html index d082a003..557d69fd 100644 --- a/frontend/src/app/_pages/profile/profile.component.html +++ b/frontend/src/app/_pages/profile/profile.component.html @@ -30,12 +30,14 @@ <label class="small mb-1" for="inputPassword">Važeća lozinka</label> <input class="form-control" id="inputPassword" name="inputPassword" type="password" [(ngModel)]="this.oldPass" placeholder="Trenutna lozinka"> <small *ngIf="wrongPassBool" class="form-text text-danger">Neispravna lozinka.</small> + <small *ngIf="wrongOldPassBool" class="form-text text-danger">Pogrešan format.</small> </div> <!-- Form Group (new password)--> <div class="col-md-6"> <label class="small mb-1" for="inputNewPassword">Nova lozinka</label> <input class="form-control" id="inputNewPassword" name="inputNewPassword" type="password" [(ngModel)]="this.newPass1" placeholder="Ukucaj novu lozinku"> <small *ngIf="wrongNewPassBool" class="form-text text-danger">Lozinke se ne podudaraju.</small> + <small *ngIf="wrongNewPass1Bool" class="form-text text-danger">Pogrešan format.</small> </div> </div> @@ -52,6 +54,7 @@ <label class="small mb-1" for="inputNewPasswordAgain">Ponovo nova lozinka</label> <input class="form-control" id="inputNewPasswordAgain" name="inputNewPasswordAgain" type="password" [(ngModel)]="this.newPass2" placeholder="Ukucaj novu lozinku"> <small *ngIf="wrongNewPassBool" class="form-text text-danger">Lozinke se ne podudaraju.</small> + <small *ngIf="wrongNewPass2Bool" class="form-text text-danger">Pogrešan format.</small> </div> </div> </div> @@ -73,11 +76,13 @@ <div class="col-md-6"> <label class="small mb-1" for="inputUsername">Korisničko ime (kako će ostali korisnici videti tvoje ime)</label> <input class="form-control" id="inputUsername" name="inputUsername" type="text" [(ngModel)]="this.username"> + <small *ngIf="wrongUsernameBool" class="form-text text-danger">Pogrešan format.</small> </div> <!-- Form Group (email address)--> <div class="col-md-6"> <label class="small mb-1" for="inputEmailAddress">Email adresa</label> <input class="form-control" id="inputEmailAddress" name="inputEmailAddress" type="email" [(ngModel)]="this.email"> + <small *ngIf="wrongEmailBool" class="form-text text-danger">Pogrešan format.</small> </div> </div> @@ -87,11 +92,13 @@ <div class="col-md-6"> <label class="small mb-1" for="inputFirstName">Ime</label> <input class="form-control" id="inputFirstName" name="inputFirstName" type="text" [(ngModel)]="this.firstName"> + <small *ngIf="wrongFirstNameBool" class="form-text text-danger">Pogrešan format.</small> </div> <!-- Form Group (last name)--> <div class="col-md-6"> <label class="small mb-1" for="inputLastName">Prezime</label> <input class="form-control" id="inputLastName" name="inputLastName" type="text" [(ngModel)]="this.lastName"> + <small *ngIf="wrongLastNameBool" class="form-text text-danger">Pogrešan format.</small> </div> </div> diff --git a/frontend/src/app/_pages/profile/profile.component.ts b/frontend/src/app/_pages/profile/profile.component.ts index 3e9a0d11..d055fad3 100644 --- a/frontend/src/app/_pages/profile/profile.component.ts +++ b/frontend/src/app/_pages/profile/profile.component.ts @@ -6,6 +6,7 @@ import { Router } from '@angular/router'; import { PICTURES } from 'src/app/_data/ProfilePictures'; import { Picture } from 'src/app/_data/ProfilePictures'; import shared from '../../Shared'; +import { share } from 'rxjs'; @Component({ @@ -71,6 +72,9 @@ export class ProfileComponent implements OnInit { } saveInfoChanges() { + if (!(this.checkInfoChanges())) //nije prosao regex + return; + let editedUser: User = { _id: this.user._id, username: this.username, @@ -84,18 +88,20 @@ export class ProfileComponent implements OnInit { this.userInfoService.changeUserInfo(editedUser).subscribe((response: any) =>{ if (this.user.username != editedUser.username) { //promenio username, ide logout this.user = editedUser; - alert("Nakon promene korisničkog imena, moraćete ponovo da se ulogujete."); + this.resetInfo(); + shared.openDialog("Obaveštenje", "Nakon promene korisničkog imena, moraćete ponovo da se ulogujete."); this.authService.logOut(); this.router.navigate(['']); return; } + shared.openDialog("Obaveštenje", "Podaci su uspešno promenjeni."); this.user = editedUser; console.log(this.user); this.resetInfo(); }, (error: any) =>{ if (error.error == "Username already exists!") { - alert("Ukucano korisničko ime je već zauzeto!\nIzaberite neko drugo."); - (<HTMLSelectElement>document.getElementById("inputUsername")).focus(); + shared.openDialog("Obaveštenje", "Ukucano korisničko ime je već zauzeto! Izaberite neko drugo."); + //(<HTMLSelectElement>document.getElementById("inputUsername")).focus(); //poruka obavestenja ispod inputa this.resetInfo(); } @@ -103,38 +109,41 @@ export class ProfileComponent implements OnInit { } savePasswordChanges() { + this.wrongPassBool = false; + this.wrongNewPassBool = false; + + if (!(this.checkPasswordChanges())) //nije prosao regex + return; + if (this.newPass1 == '' && this.newPass2 == '') //ne zeli da promeni lozinku return; - console.log("zeli da promeni lozinku"); + //console.log("zeli da promeni lozinku"); if (this.newPass1 != this.newPass2) { //netacno ponovio novu lozinku this.wrongNewPassBool = true; this.resetNewPassInputs(); - console.log("Netacno ponovljena lozinka"); + //console.log("Netacno ponovljena lozinka"); return; } - this.wrongPassBool = false; - this.wrongNewPassBool = false; - let passwordArray: string[] = [this.oldPass, this.newPass1]; this.userInfoService.changeUserPassword(passwordArray).subscribe((response: any) => { - console.log("PROMENIO LOZINKU"); + //console.log("PROMENIO LOZINKU"); this.resetNewPassInputs(); - alert("Nakon promene lozinke, moraćete ponovo da se ulogujete."); + shared.openDialog("Obaveštenje", "Nakon promene lozinke, moraćete ponovo da se ulogujete."); this.authService.logOut(); this.router.navigate(['']); }, (error: any) => { console.log("error poruka: ", error.error); if (error.error == 'Wrong old password!') { this.wrongPassBool = true; - (<HTMLSelectElement>document.getElementById("inputPassword")).focus(); + //(<HTMLSelectElement>document.getElementById("inputPassword")).focus(); return; } else if (error.error == 'Identical password!') { - alert("Stara i nova lozinka su identične."); + shared.openDialog("Obaveštenje", "Stara i nova lozinka su identične."); this.resetNewPassInputs(); - (<HTMLSelectElement>document.getElementById("inputNewPassword")).focus(); + //(<HTMLSelectElement>document.getElementById("inputNewPassword")).focus(); return; } }); @@ -161,5 +170,95 @@ export class ProfileComponent implements OnInit { shared.photoId = this.photoId; } + checkPasswordChanges() : boolean { + this.passwordValidation(); + + if (!(this.wrongOldPassBool || this.wrongNewPass1Bool || this.wrongNewPass2Bool)) + return true; + return false; + } + checkInfoChanges() : boolean { + this.firstName = this.firstName.trim(); + this.lastName = this.lastName.trim(); + this.username = this.username.trim(); + this.email = this.email.trim(); + + this.firstNameValidation(); + this.lastNameValidation(); + this.usernameValidation(); + this.emailValidation(); + + if (!(this.wrongUsernameBool || this.wrongEmailBool || this.wrongFirstNameBool || this.wrongLastNameBool)) + return true; + return false; + } + + isCorrectName(element: string): boolean { + if (this.pattName.test(element) && !(this.pattTwoSpaces.test(element)) && (element.length >= 1 && element.length <= 30)) + return true; + return false; + } + isCorrectUsername(element: string): boolean { + if (this.pattUsername.test(element) && !(this.pattTwoSpaces.test(element)) && (element.length >= 1 && element.length <= 30)) + return true; + return false; + } + isCorrectEmail(element: string): boolean { + if (this.pattEmail.test(element.toLowerCase()) && element.length <= 320) + return true; + return false; + } + isCorrectPassword(element: string): boolean { + if (this.pattPassword.test(element)) + return true; + return false; + } + firstNameValidation() { + if (this.isCorrectName(this.firstName) == true) { + this.wrongFirstNameBool = false; + return; + } + //(<HTMLSelectElement>document.getElementById('firstName')).focus(); + this.wrongFirstNameBool = true; + } + lastNameValidation() { + if (this.isCorrectName(this.lastName) == true) { + this.wrongLastNameBool = false; + return; + } + //(<HTMLSelectElement>document.getElementById('lastName')).focus(); + this.wrongLastNameBool = true; + } + usernameValidation() { + if (this.isCorrectUsername(this.username) == true) { + this.wrongUsernameBool = false; + return; + } + //(<HTMLSelectElement>document.getElementById('username-register')).focus(); + this.wrongUsernameBool = true; + } + emailValidation() { + if (this.isCorrectEmail(this.email) == true) { + this.wrongEmailBool = false; + return; + } + //(<HTMLSelectElement>document.getElementById('email')).focus(); + this.wrongEmailBool = true; + } + passwordValidation() { + if (this.isCorrectPassword(this.oldPass) && this.isCorrectPassword(this.newPass1) && this.newPass1 == this.newPass2) { + this.wrongOldPassBool = false; + this.wrongNewPass1Bool = false; + this.wrongNewPass2Bool = false; + return; + } + this.oldPass = ''; + this.newPass1 = ''; + this.newPass2 = ''; + //(<HTMLSelectElement>document.getElementById('pass1')).focus(); + this.wrongOldPassBool = true; + this.wrongNewPass1Bool = true; + this.wrongNewPass2Bool = true; + } } diff --git a/frontend/src/app/_services/datasets.service.ts b/frontend/src/app/_services/datasets.service.ts index 0ff63828..cd901481 100644 --- a/frontend/src/app/_services/datasets.service.ts +++ b/frontend/src/app/_services/datasets.service.ts @@ -27,4 +27,12 @@ export class DatasetsService { getDatasetFile(fileId: any): any { return this.http.get(`${API_SETTINGS.apiURL}/file/download?id=${fileId}`, { headers: this.authService.authHeader(), responseType: 'text' }); } + + editDataset(dataset: Dataset): Observable<Dataset> { + return this.http.put<Dataset>(`${API_SETTINGS.apiURL}/dataset/`, dataset, { headers: this.authService.authHeader() }); + } + + deleteDataset(dataset: Dataset) { + return this.http.delete(`${API_SETTINGS.apiURL}/dataset/` + dataset.name, { headers: this.authService.authHeader(), responseType: "text" }); + } } diff --git a/frontend/src/app/_services/predictors.service.ts b/frontend/src/app/_services/predictors.service.ts index 54ae5694..844cd706 100644 --- a/frontend/src/app/_services/predictors.service.ts +++ b/frontend/src/app/_services/predictors.service.ts @@ -3,6 +3,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { API_SETTINGS } from 'src/config'; import Predictor from '../_data/Predictor'; +import { Column } from '../_pages/predict/predict.component'; import { AuthService } from './auth.service'; @Injectable({ @@ -21,7 +22,15 @@ export class PredictorsService { return this.http.get<Predictor>(`${API_SETTINGS.apiURL}/predictor/getpredictor/`+ id, { headers: this.authService.authHeader() }); } - usePredictor(predictor: Predictor, inputs : String[]) { + usePredictor(predictor: Predictor, inputs : Column[]) { return this.http.post(`${API_SETTINGS.apiURL}/predictor/usepredictor/` + predictor._id, inputs, { headers: this.authService.authHeader() }); } + + deletePredictor(predictor: Predictor) { + return this.http.delete(`${API_SETTINGS.apiURL}/predictor/` + predictor.name, { headers: this.authService.authHeader(), responseType: "text" }); + } + + getMyPredictors(): Observable<Predictor[]> { + return this.http.get<Predictor[]>(`${API_SETTINGS.apiURL}/predictor/mypredictors`, { headers: this.authService.authHeader() }); + } } diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index d701f9d7..04523989 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -1,4 +1,4 @@ -import { NgModule } from '@angular/core'; +import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { FormsModule } from '@angular/forms'; import { AppRoutingModule } from './app-routing.module'; @@ -42,6 +42,8 @@ import { AnnvisualComponent } from './_elements/annvisual/annvisual.component'; import { ExperimentComponent } from './experiment/experiment.component'; import { LoadingComponent } from './_elements/loading/loading.component'; import { ModelLoadComponent } from './_elements/model-load/model-load.component'; +import { AlertDialogComponent } from './_modals/alert-dialog/alert-dialog.component'; +import { AddNewDatasetComponent } from './_elements/add-new-dataset/add-new-dataset.component'; @NgModule({ declarations: [ @@ -73,7 +75,9 @@ import { ModelLoadComponent } from './_elements/model-load/model-load.component' AnnvisualComponent, ExperimentComponent, LoadingComponent, - ModelLoadComponent + ModelLoadComponent, + AlertDialogComponent, + AddNewDatasetComponent ], imports: [ BrowserModule, @@ -90,6 +94,8 @@ import { ModelLoadComponent } from './_elements/model-load/model-load.component' Ng2SearchPipeModule, ], providers: [], - bootstrap: [AppComponent] + bootstrap: [AppComponent], + schemas: [CUSTOM_ELEMENTS_SCHEMA], + entryComponents: [AlertDialogComponent] }) export class AppModule { } diff --git a/frontend/src/app/material.module.ts b/frontend/src/app/material.module.ts index e85419ee..d16cef3d 100644 --- a/frontend/src/app/material.module.ts +++ b/frontend/src/app/material.module.ts @@ -1,18 +1,21 @@ import { NgModule } from '@angular/core'; - +import { CommonModule } from '@angular/common'; import { MatDialogModule } from '@angular/material/dialog'; import { MatButtonModule } from '@angular/material/button'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatIconModule } from '@angular/material/icon'; @NgModule({ exports: [ + CommonModule, MatDialogModule, MatButtonModule, MatFormFieldModule, MatInputModule, - MatCheckboxModule + MatCheckboxModule, + MatIconModule ] }) export class MaterialModule {}
\ No newline at end of file |