aboutsummaryrefslogtreecommitdiff
path: root/frontend
diff options
context:
space:
mode:
authorOgnjen Cirkovic <ciraboxkg@gmail.com>2022-03-22 15:04:36 +0000
committerOgnjen Cirkovic <ciraboxkg@gmail.com>2022-03-22 15:04:36 +0000
commit012fb19a54f4d55a6e4cc73227f738f64539cf04 (patch)
tree57b3de84ad41037e8c7b1403dba4a5bad24a4752 /frontend
parentb4f0cd025a86c68a5c35a58e62c22b7cedf3d8b5 (diff)
parent31642f68564e67175301235546b74baf56ac5882 (diff)
Merge branch 'dev' into 'Privremeno-cuvanje-podataka'
# Conflicts: # backend/api/api/Controllers/ModelController.cs
Diffstat (limited to 'frontend')
-rw-r--r--frontend/package-lock.json46
-rw-r--r--frontend/package.json4
-rw-r--r--frontend/src/app/_data/Model.ts50
-rw-r--r--frontend/src/app/_elements/dataset-load/dataset-load.component.html4
-rw-r--r--frontend/src/app/_elements/dataset-load/dataset-load.component.ts11
-rw-r--r--frontend/src/app/_elements/item-dataset/item-dataset.component.html2
-rw-r--r--frontend/src/app/_modals/login-modal/login-modal.component.html10
-rw-r--r--frontend/src/app/_modals/login-modal/login-modal.component.ts23
-rw-r--r--frontend/src/app/_modals/register-modal/register-modal.component.html2
-rw-r--r--frontend/src/app/_modals/register-modal/register-modal.component.ts16
-rw-r--r--frontend/src/app/_pages/add-model/add-model.component.css17
-rw-r--r--frontend/src/app/_pages/add-model/add-model.component.html118
-rw-r--r--frontend/src/app/_pages/add-model/add-model.component.ts177
-rw-r--r--frontend/src/app/_services/models.service.ts4
-rw-r--r--frontend/src/app/app.component.html4
-rw-r--r--frontend/src/app/app.module.ts11
-rw-r--r--frontend/src/app/barchart/barchart.component.css6
-rw-r--r--frontend/src/app/barchart/barchart.component.html4
-rw-r--r--frontend/src/app/barchart/barchart.component.spec.ts25
-rw-r--r--frontend/src/app/barchart/barchart.component.ts54
-rw-r--r--frontend/src/app/scatterchart/scatterchart.component.css6
-rw-r--r--frontend/src/app/scatterchart/scatterchart.component.html4
-rw-r--r--frontend/src/app/scatterchart/scatterchart.component.spec.ts25
-rw-r--r--frontend/src/app/scatterchart/scatterchart.component.ts32
-rw-r--r--frontend/src/index.html1
-rw-r--r--frontend/src/styles.css1
26 files changed, 589 insertions, 68 deletions
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 5b23f3d2..0333f749 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -24,9 +24,11 @@
"@ng-bootstrap/ng-bootstrap": "^12.0.0",
"@popperjs/core": "^2.10.2",
"bootstrap": "^5.1.3",
+ "chart.js": "^3.7.1",
"csv-parser": "^3.0.0",
"mdb-angular-ui-kit": "^2.0.0",
"ng-uikit-pro-standard": "^1.0.0",
+ "ng2-charts": "^3.0.8",
"ngx-cookie-service": "^13.1.2",
"ngx-csv-parser": "^0.0.7",
"rxjs": "~7.5.0",
@@ -3792,6 +3794,11 @@
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true
},
+ "node_modules/chart.js": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.7.1.tgz",
+ "integrity": "sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA=="
+ },
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -7376,6 +7383,11 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+ },
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -7949,6 +7961,21 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
},
+ "node_modules/ng2-charts": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-3.0.8.tgz",
+ "integrity": "sha512-ELlpN0b/IJO4ka/P2sFBKeng3bV7XOQuh40f0J5hx9UveWPaSxOYQAOiGxV7BN2VSnKq6GRkjRvqTrcQPyJYww==",
+ "dependencies": {
+ "lodash-es": "^4.17.15",
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "@angular/common": ">=11.0.0",
+ "@angular/core": ">=11.0.0",
+ "chart.js": "^3.4.0",
+ "rxjs": "^6.5.3 || ^7.4.0"
+ }
+ },
"node_modules/ngx-cookie-service": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-13.1.2.tgz",
@@ -14259,6 +14286,11 @@
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true
},
+ "chart.js": {
+ "version": "3.7.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.7.1.tgz",
+ "integrity": "sha512-8knRegQLFnPQAheZV8MjxIXc5gQEfDFD897BJgv/klO/vtIyFFmgMXrNfgrXpbTr/XbTturxRgxIXx/Y+ASJBA=="
+ },
"chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
@@ -16856,6 +16888,11 @@
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
+ "lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+ },
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -17286,6 +17323,15 @@
}
}
},
+ "ng2-charts": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/ng2-charts/-/ng2-charts-3.0.8.tgz",
+ "integrity": "sha512-ELlpN0b/IJO4ka/P2sFBKeng3bV7XOQuh40f0J5hx9UveWPaSxOYQAOiGxV7BN2VSnKq6GRkjRvqTrcQPyJYww==",
+ "requires": {
+ "lodash-es": "^4.17.15",
+ "tslib": "^2.3.0"
+ }
+ },
"ngx-cookie-service": {
"version": "13.1.2",
"resolved": "https://registry.npmjs.org/ngx-cookie-service/-/ngx-cookie-service-13.1.2.tgz",
diff --git a/frontend/package.json b/frontend/package.json
index 01cdf462..c3f0310e 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -26,9 +26,11 @@
"@ng-bootstrap/ng-bootstrap": "^12.0.0",
"@popperjs/core": "^2.10.2",
"bootstrap": "^5.1.3",
+ "chart.js": "^3.7.1",
"csv-parser": "^3.0.0",
"mdb-angular-ui-kit": "^2.0.0",
"ng-uikit-pro-standard": "^1.0.0",
+ "ng2-charts": "^3.0.8",
"ngx-cookie-service": "^13.1.2",
"ngx-csv-parser": "^0.0.7",
"rxjs": "~7.5.0",
@@ -49,4 +51,4 @@
"karma-jasmine-html-reporter": "~1.7.0",
"typescript": "~4.5.2"
}
-} \ No newline at end of file
+}
diff --git a/frontend/src/app/_data/Model.ts b/frontend/src/app/_data/Model.ts
index a891c10c..0768a374 100644
--- a/frontend/src/app/_data/Model.ts
+++ b/frontend/src/app/_data/Model.ts
@@ -12,7 +12,7 @@ export default class Model {
public columnToPredict: string = '',
public randomOrder: boolean = true,
public randomTestSet: boolean = true,
- public randomTestSetDistribution: number = 0.10, //0.1-0.9 (10% - 90%)
+ public randomTestSetDistribution: number = 0.1, //0.1-0.9 (10% - 90%) JESTE OVDE ZAKUCANO 10, AL POSLATO JE KAO 0.1 BACK-U
// Neural net training settings
public type: ANNType = ANNType.FullyConnected,
@@ -39,21 +39,61 @@ export enum ANNType {
// removeOutliers
export enum Encoding {
Label = 'label',
- OneHot = 'one hot'
+ OneHot = 'one hot',
+ BackwardDifference = 'backward difference',
+ BaseN = 'baseN',
+ Binary = 'binary',
+ CatBoost = 'cat boost',
+ Count = 'count',
+ GLMM = 'glmm',
+ Hashing = 'hashing',
+ Helmert = 'helmert',
+ JamesStein = 'james stein',
+ LeaveOneOut = 'leave one out',
+ MEstimate = 'MEstimate',
+ Ordinal = 'ordinal',
+ Sum = 'sum',
+ Polynomial = 'polynomial',
+ Target = 'target',
+ WOE = 'woe',
+ Quantile = 'quantile'
}
export enum ActivationFunction {
+ // linear
+ Binary_Step = 'binaryStep',
+ Linear = 'linear',
+ // non-linear
Relu = 'relu',
+ Leaky_Relu = 'leakyRelu',
+ Parameterised_Relu = 'parameterisedRelu',
+ Exponential_Linear_Unit = 'exponentialLinearUnit',
+ Swish = 'swish',
Sigmoid = 'sigmoid',
Tanh = 'tanh',
- Linear = 'linear'
+ Softmax = 'softmax'
}
export enum LossFunction {
+ // binary classification loss functions
BinaryCrossEntropy = 'binary_crossentropy',
- MeanSquaredError = 'mean_squared_error'
+ HingeLoss = 'hinge_loss',
+ // multi-class classiication loss functions
+ CategoricalCrossEntropy = 'categorical_crossentropy',
+ KLDivergence = 'kullback_leibler_divergence',
+ // regression loss functions
+ MeanSquaredError = 'mean_squared_error',
+ MeanAbsoluteError = 'mean_absolute_error',
+ HuberLoss = 'Huber',
}
export enum Optimizer {
- Adam = 'adam'
+ Adam = 'Adam',
+ Adadelta = 'Adadelta',
+ Adagrad = 'Adagrad',
+ Ftrl = 'Ftrl',
+ Nadam = 'Nadam',
+ SGD = 'SGD',
+ SGDMomentum = 'SGDMomentum',
+ RMSprop = 'RMSprop'
} \ 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 fcec6ec3..76fc40e2 100644
--- a/frontend/src/app/_elements/dataset-load/dataset-load.component.html
+++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.html
@@ -30,7 +30,7 @@
</div>
</div>
- <div class="table-responsive">
+ <div class="table-responsive" *ngIf="hasInput">
<table *ngIf="csvRecords.length > 0 && hasHeader" class="table table-bordered table-light mt-4">
<thead>
<tr>
@@ -53,7 +53,7 @@
</table>
</div>
- <div *ngIf="csvRecords.length > 0" id="info">
+ <div *ngIf="csvRecords.length > 0 && hasInput" id="info">
. . . <br>
{{rowsNumber}} x {{colsNumber}}
</div>
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 913592eb..bccf13b7 100644
--- a/frontend/src/app/_elements/dataset-load/dataset-load.component.ts
+++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts
@@ -16,7 +16,7 @@ export class DatasetLoadComponent {
hasHeader: boolean = true;
- slice: string = "";
+ hasInput: boolean = false;
csvRecords: any[] = [];
files: File[] = [];
@@ -33,6 +33,15 @@ export class DatasetLoadComponent {
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;
+
console.log(this.files);
this.update();
}
diff --git a/frontend/src/app/_elements/item-dataset/item-dataset.component.html b/frontend/src/app/_elements/item-dataset/item-dataset.component.html
index cf39a125..46840cdd 100644
--- a/frontend/src/app/_elements/item-dataset/item-dataset.component.html
+++ b/frontend/src/app/_elements/item-dataset/item-dataset.component.html
@@ -2,7 +2,7 @@
<div class="card-header">
{{dataset.name}}
</div>
- <div class="card-body">
+ <div class="card-body overflow-hidden">
<p class="card-text">
{{dataset.description}}
</p>
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 d694ea58..d7836848 100644
--- a/frontend/src/app/_modals/login-modal/login-modal.component.html
+++ b/frontend/src/app/_modals/login-modal/login-modal.component.html
@@ -20,12 +20,12 @@
<input [(ngModel)]="password" name="password" type="password" id="password"
class="form-control form-control" placeholder="Unesite lozinku..." />
</div>
-
- <div class="text-center text-lg-start mt-5 pt-2">
- <p *ngIf="wrongCreds" class="small fw-bold mt-2 pt-1 mb-0 text-danger">Lozinka ili e-mail su pogrešni
- </p>
- </div>
</form>
+
+ <div class="text-center text-lg-start mt-5">
+ <p *ngIf="wrongCreds" class="small fw-bold text-danger text-center">Unesite ispravan e-mail i lozinku.</p>
+ </div>
+
<div class="col-md-12 d-flex justify-content-center">
<button type="button" class="btn btn-lg" style="color:white; background-color: #003459; margin-right: 10px;" (click)="doLogin()">Prijavite se</button>
<button type="button" class="btn btn-lg btn-outline-secondary" data-bs-dismiss="modal" (click)="resetData()">Odustanite</button>
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 d17d7017..1b634c9a 100644
--- a/frontend/src/app/_modals/login-modal/login-modal.component.ts
+++ b/frontend/src/app/_modals/login-modal/login-modal.component.ts
@@ -13,7 +13,7 @@ export class LoginModalComponent implements OnInit {
username: string = '';
password: string = '';
- public wrongCreds: boolean = false; //RAZMOTRITI
+ wrongCreds: boolean = false;
constructor(
private authService: AuthService,
@@ -26,17 +26,26 @@ export class LoginModalComponent implements OnInit {
doLogin() {
if (this.username.length > 0 && this.password.length > 0) {
- this.authService.login(this.username, this.password).subscribe((response) => { //ako nisu ok podaci, ne ide hide nego mora opet da ukucava!!!!podesi
+ this.authService.login(this.username, this.password).subscribe((response) => {
console.log(response);
- this.authService.authenticate(response);
- (<HTMLSelectElement>document.getElementById('closeButton')).click();
- }, error => {
- console.warn(error); //NETACNI PODACI
+
+ if (response == "Username doesn't exist" || response == "Wrong password") {
+ this.wrongCreds = true;
+ this.password = '';
+ }
+ else {
+ this.authService.authenticate(response);
+ (<HTMLSelectElement>document.getElementById('closeButton')).click();
+ }
});
}
-
+ else {
+ this.wrongCreds = true;
+ this.password = '';
+ }
}
resetData() {
+ this.wrongCreds = false;
this.username = '';
this.password = '';
}
diff --git a/frontend/src/app/_modals/register-modal/register-modal.component.html b/frontend/src/app/_modals/register-modal/register-modal.component.html
index 7098c040..68025a46 100644
--- a/frontend/src/app/_modals/register-modal/register-modal.component.html
+++ b/frontend/src/app/_modals/register-modal/register-modal.component.html
@@ -4,7 +4,7 @@
<div class="modal-dialog modal-dialog-centered modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header" style="background-color: #003459;">
- <button type="button" class="btn-close" data-bs-dismiss="modal" style="background-color: white;"
+ <button id="closeButtonReg" type="button" class="btn-close" data-bs-dismiss="modal" style="background-color: white;"
aria-label="Close" (click)="resetData()"></button>
</div>
<div class="modal-body" style="color:#003459">
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 c02a4e1a..c045f1ce 100644
--- a/frontend/src/app/_modals/register-modal/register-modal.component.ts
+++ b/frontend/src/app/_modals/register-modal/register-modal.component.ts
@@ -136,15 +136,21 @@ export class RegisterModalComponent implements OnInit {
.subscribe(
(response) => {
console.log(response);
- if (response === 'User added') {
- this.resetData();
- (<HTMLSelectElement>document.getElementById('linkToLoginModal')).click();
+ if (response == 'User added') {
+ //nakon sto je registrovan, nek bude ulogovan
+ this.authService.login(this.username, this.pass1).subscribe((response) => {
+
+ this.authService.authenticate(response);
+ console.log("close button");
+ (<HTMLSelectElement>document.getElementById('closeButtonReg')).click();
+ //(<HTMLSelectElement>document.getElementById('linkToLoginModal')).click();
+ }, (error) => console.warn(error));
}
- else if (response === 'Email Already Exists') {
+ else if (response == 'Email Already Exists') {
alert('Nalog sa unetim email-om već postoji!');
(<HTMLSelectElement>document.getElementById('email')).focus();
}
- else if (response === 'Username Already Exists') {
+ else if (response == 'Username Already Exists') {
alert('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 4bf569cc..6d961287 100644
--- a/frontend/src/app/_pages/add-model/add-model.component.css
+++ b/frontend/src/app/_pages/add-model/add-model.component.css
@@ -15,4 +15,21 @@
#wrapper {
color: #003459;
+}
+
+.btnType1 {
+ background-color: #003459;
+ color: white;
+}
+.btnType2 {
+ background-color: white;
+ color: #003459;
+ border-color: #003459;
+}
+.selectedDatasetClass {
+ /*border-color: 2px solid #003459;*/
+ background-color: lightblue;
+}
+ul li:hover {
+ background-color: lightblue;
} \ No newline at end of file
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 c6f21f1e..33066f80 100644
--- a/frontend/src/app/_pages/add-model/add-model.component.html
+++ b/frontend/src/app/_pages/add-model/add-model.component.html
@@ -6,7 +6,7 @@
<div id="container" class="container p-5" style="background-color: white; min-height: 100%;">
- <div class="form-group row mb-4 d-flex justify-content-center">
+ <div class="form-group row mt-3 mb-2 d-flex justify-content-center">
<!--justify-content-center-->
<h2 class="col-2"> Nov model: </h2>
<div class="col-3">
@@ -26,22 +26,87 @@
</div>
</div>
- <div class="mt-5 justify-content-center">
- <h2>Izvor podataka:</h2>
- <app-dataset-load id="dataset" (loaded)="datasetLoaded = true"></app-dataset-load>
+ <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>
+
+ <!-- POSTOJECI ILI NOVI DATASET -->
+
+ <!-- POSTOJECI -->
+ <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" [ngClass]="{'selectedDatasetClass': this.selectedDataset == dataset}">
+ <app-item-dataset name="usersDataset" [dataset]="dataset" (click)="selectThisDataset(dataset)"></app-item-dataset>
+ </li>
+ </ul>
+ </div>
+ </div>
+
+ <!-- NOVI -->
+ <app-dataset-load *ngIf="!showMyDatasets" id="dataset" (loaded)="datasetLoaded = true"></app-dataset-load>
</div>
- <div *ngIf="datasetLoaded">
- <div *ngIf="datasetLoadComponent" class="row">
+
+ <!-- ULAZNE/IZLAZNE KOLONE - POSTOJECI DATASET -->
+ <div *ngIf="showMyDatasets && this.selectedDataset" class="mt-4">
+ <h2 class="text-center">
+ Izabrali ste dataset: <span style="color: #003459; font-weight: bold">{{this.selectedDataset.name}}</span>
+ </h2>
+ <div class="row mt-5">
+ <div class="col d-flex justify-content-center">
+ <h3>Izaberite ulazne kolone:</h3>
+ <div id="divInputs" class="form-check mt-2">
+ <br>
+ <div *ngFor="let item of this.selectedDataset.header; let i = index">
+ <input class="form-check-input" type="checkbox" value="{{item}}"
+ id="cb_{{item}}" name="cbsExisting" checked [disabled]="this.selectedOutputColumnVal == item">&nbsp;
+ <label class="form-check-label" for="cb_{{item}}">
+ {{item}}
+ </label>
+ </div>
+ </div>
+ </div>
+ <div class="col d-flex justify-content-left">
+ <h3>Izaberite izlaznu kolonu:</h3>
+ <div id="divOutputs" class="form-check mt-2">
+ <br>
+ <div *ngFor="let item of this.selectedDataset.header; let i = index">
+ <input class="form-check-input" type="radio" value="{{item}}"
+ id="rb_{{item}}" name="rbsExisting" (change)="this.selectedOutputColumnVal = item">&nbsp;
+ <label class="form-check-label" for="rb_{{item}}">
+ {{item}}
+ </label>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+
+ <!-- ULAZNE/IZLAZNE KOLONE - NOVI DATASET-->
+ <div *ngIf="!showMyDatasets && datasetLoaded">
+ <div *ngIf="datasetLoadComponent && datasetLoadComponent.files[0]" class="row">
<div class="col d-flex justify-content-center">
<h3>Izaberite ulazne kolone:</h3>
<div id="divInputs" class="form-check mt-2">
<br>
<div *ngFor="let item of datasetLoadComponent.dataset.header; let i = index">
- <input *ngIf="i == 0" class="form-check-input" type="checkbox" value="{{item}}"
- id="cb_{{item}}" name="cbs" checked>
- <input *ngIf="i != 0" class="form-check-input" type="checkbox" value="{{item}}"
- id="cb_{{item}}" name="cbs">&nbsp;
+ <input class="form-check-input" type="checkbox" value="{{item}}"
+ id="cb_{{item}}" name="cbsNew" checked [disabled]="this.selectedOutputColumnVal == item">&nbsp;
<label class="form-check-label" for="cb_{{item}}">
{{item}}
</label>
@@ -53,10 +118,8 @@
<div id="divOutputs" class="form-check mt-2">
<br>
<div *ngFor="let item of datasetLoadComponent.dataset.header; let i = index">
- <input *ngIf="i == 0" class="form-check-input" type="radio" value="{{item}}"
- id="rb_{{item}}" name="rbs" checked>
- <input *ngIf="i != 0" class="form-check-input" type="radio" value="{{item}}"
- id="rb_{{item}}" name="rbs">&nbsp;
+ <input class="form-check-input" type="radio" value="{{item}}"
+ id="rb_{{item}}" name="rbsNew" (change)="this.selectedOutputColumnVal = item">&nbsp;
<label class="form-check-label" for="rb_{{item}}">
{{item}}
</label>
@@ -193,15 +256,10 @@
<div class="col-1">
</div>
<div class="col-5">
- <label for="type" class="form-check-label">Podela test skupa:&nbsp;&nbsp;
- <input class="form-check-input" type="checkbox" [checked]="newModel.randomTestSet"
+ <label for="splitYesNo" class="form-check-label">Podela test skupa:&nbsp;&nbsp;
+ <input id="splitYesNo" class="form-check-input" type="checkbox" [checked]="newModel.randomTestSet"
(change)="newModel.randomTestSet = !newModel.randomTestSet">
- </label> &nbsp;&nbsp;&nbsp;&nbsp;
- test
- <mat-slider min="0.1" max="0.9" step="0.1" value="0.2" name="randomTestSetDistribution" thumbLabel
- [disabled]="!newModel.randomTestSet" [(ngModel)]="newModel.randomTestSetDistribution">
- </mat-slider>
- trening
+ </label>
</div>
<div class="col">
</div>
@@ -224,9 +282,14 @@
</option>
</select>
</div>
- <div class="col">
+ <div class="col-1">
</div>
- <div class="col">
+ <div class="col-2">
+ <label for="percentage" class="form-label">Procenat podataka koji se uzima za trening skup:</label>
+ </div>
+ <div class="col-1">
+ <input id="percentage" type="number" class="form-control" min="10" max="90" step="10" value="90" [(ngModel)]="tempTestSetDistribution"
+ [disabled] = "!newModel.randomTestSet">
</div>
</div>
@@ -247,7 +310,14 @@
</option>
</select>
</div>
+ <div class="col-1">
+ </div>
<div class="col">
+ trening
+ <mat-slider min="10" max="90" step="10" value="10" name="randomTestSetDistribution" thumbLabel
+ [disabled]="!newModel.randomTestSet" [(ngModel)]="tempTestSetDistribution">
+ </mat-slider>
+ test
</div>
<div class="col">
</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 a4cabb82..7bfb7204 100644
--- a/frontend/src/app/_pages/add-model/add-model.component.ts
+++ b/frontend/src/app/_pages/add-model/add-model.component.ts
@@ -1,10 +1,10 @@
import { Component, OnInit, ViewChild } from '@angular/core';
-import { Observable, of } from 'rxjs';
import Model from 'src/app/_data/Model';
import { ANNType, Encoding, ActivationFunction, LossFunction, Optimizer } from 'src/app/_data/Model';
import { DatasetLoadComponent } from 'src/app/_elements/dataset-load/dataset-load.component';
import { ModelsService } from 'src/app/_services/models.service';
import shared from 'src/app/Shared';
+import Dataset from 'src/app/_data/Dataset';
@Component({
@@ -27,63 +27,145 @@ export class AddModelComponent implements OnInit {
Object = Object;
shared = shared;
+ selectedOutputColumnVal: string = '';
+
+ showMyDatasets: boolean = true;
+ myDatasets?: Dataset[];
+ existingDatasetSelected: boolean = false;
+ selectedDataset?: Dataset;
+
+ tempTestSetDistribution: number = 90;
+
constructor(private models: ModelsService) {
this.newModel = new Model();
+
+ this.models.getMyDatasets().subscribe((datasets) => {
+ this.myDatasets = datasets;
+ });
}
ngOnInit(): void {
+ (<HTMLInputElement>document.getElementById("btnMyDataset")).focus();
+ }
+
+ viewMyDatasetsForm() {
+ this.showMyDatasets = true;
+ this.resetSelectedDataset();
+ this.datasetLoaded = false;
+ this.resetCbsAndRbs();
+ }
+ viewNewDatasetForm() {
+ this.showMyDatasets = false;
+ this.resetSelectedDataset();
+ this.resetCbsAndRbs();
}
addModel() {
- this.saveModel(false); //trajno cuvanje
+ if (!this.showMyDatasets)
+ this.saveModelWithNewDataset();
+ else
+ this.saveModelWithExistingDataset();
}
trainModel() {
- this.saveModel(true).subscribe((modelId: any) => {
+ this.saveModelWithNewDataset().subscribe((modelId: any) => {
if (modelId)
this.models.trainModel(modelId);
}); //privremeno cuvanje modela => vraca id sacuvanog modela koji cemo da treniramo sad
}
- saveModel(temporary: boolean): any {
+ saveModelWithNewDataset(): any {
+
this.getCheckedInputCols();
this.getCheckedOutputCol();
+
if (this.validationInputsOutput()) {
console.log('ADD MODEL: STEP 1 - UPLOAD FILE');
if (this.datasetLoadComponent) {
+
this.models.uploadData(this.datasetLoadComponent.files[0]).subscribe((file) => {
console.log('ADD MODEL: STEP 2 - ADD DATASET WITH FILE ID ' + file._id);
if (this.datasetLoadComponent) {
this.datasetLoadComponent.dataset.fileId = file._id;
this.datasetLoadComponent.dataset.username = shared.username;
+
this.models.addDataset(this.datasetLoadComponent.dataset).subscribe((dataset) => {
console.log('ADD MODEL: STEP 3 - ADD MODEL WITH DATASET ID ', dataset._id);
this.newModel.datasetId = dataset._id;
+
+ //da se doda taj dataset u listu postojecih, da bude izabran
+ this.refreshMyDatasetList();
+ this.showMyDatasets = true;
+ this.selectThisDataset(dataset);
+
+ this.newModel.randomTestSetDistribution = 1 - Math.round(this.tempTestSetDistribution / 100 * 10) / 10;
+ this.tempTestSetDistribution = 90;
this.newModel.username = shared.username;
+
this.models.addModel(this.newModel).subscribe((response) => {
console.log('ADD MODEL: DONE! REPLY:\n', response);
- });
- });
- }
+ }, (error) => {
+ alert("Model sa unetim nazivom već postoji u Vašoj kolekciji.\nPromenite naziv modela i nastavite sa kreiranim datasetom.");
+ }); //kraj addModel subscribe
+ }, (error) => {
+ alert("Dataset sa unetim nazivom već postoji u Vašoj kolekciji.\nIzmenite naziv ili iskoristite postojeći dataset.");
+ }); //kraj addDataset subscribe
+ } //kraj treceg ifa
+ }, (error) => {
+ //alert("greska uploadData");
+ }); //kraj uploadData subscribe
+
+ } //kraj drugog ifa
+ } //kraj prvog ifa
+ }
+
+ saveModelWithExistingDataset(): any {
+
+ if (this.selectedDataset) { //dataset je izabran
+ this.getCheckedInputCols();
+ this.getCheckedOutputCol();
+
+ if (this.validationInputsOutput()) {
+ this.newModel.datasetId = this.selectedDataset._id;
+
+ this.newModel.randomTestSetDistribution = 1 - Math.round(this.tempTestSetDistribution / 100 * 10) / 10;
+ this.tempTestSetDistribution = 90;
+ this.newModel.username = shared.username;
+
+ this.models.addModel(this.newModel).subscribe((response) => {
+ console.log('ADD MODEL: DONE! REPLY:\n', response);
+ }, (error) => {
+ alert("Model sa unetim nazivom već postoji u Vašoj kolekciji.\nPromenite naziv modela i nastavite sa kreiranim datasetom.");
});
}
}
+ else {
+ alert("Molimo Vas da izaberete neki dataset iz kolekcije.");
+ }
}
getCheckedInputCols() {
this.newModel.inputColumns = [];
- let checkboxes = document.getElementsByName("cbs");
-
+ let checkboxes: any;
+ if (this.showMyDatasets)
+ checkboxes = document.getElementsByName("cbsExisting");
+ else
+ checkboxes = document.getElementsByName("cbsNew");
+
for (let i = 0; i < checkboxes.length; i++) {
let thatCb = <HTMLInputElement>checkboxes[i];
- if (thatCb.checked)
+ if (thatCb.checked == true && thatCb.disabled == false)
this.newModel.inputColumns.push(thatCb.value);
}
//console.log(this.checkedInputCols);
}
getCheckedOutputCol() {
this.newModel.columnToPredict = '';
- let radiobuttons = document.getElementsByName("rbs");
+ let radiobuttons: any;
+ if (this.showMyDatasets)
+ radiobuttons = document.getElementsByName("rbsExisting");
+ else
+ radiobuttons = document.getElementsByName("rbsNew");
for (let i = 0; i < radiobuttons.length; i++) {
let thatRb = <HTMLInputElement>radiobuttons[i];
@@ -95,8 +177,16 @@ export class AddModelComponent implements OnInit {
//console.log(this.checkedOutputCol);
}
validationInputsOutput(): boolean {
- if (this.newModel.inputColumns.length == 0) {
- alert("Molimo Vas da izaberete ulaznu kolonu/kolone za mrežu.")
+ if (this.newModel.inputColumns.length == 0 && this.newModel.columnToPredict == '') {
+ alert("Molimo Vas da izaberete ulazne i izlazne kolone za mrežu.");
+ return false;
+ }
+ else if (this.newModel.inputColumns.length == 0) {
+ alert("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.");
return false;
}
for (let i = 0; i < this.newModel.inputColumns.length; i++) {
@@ -109,4 +199,65 @@ export class AddModelComponent implements OnInit {
return true;
}
+ selectThisDataset(dataset: Dataset) {
+ this.selectedDataset = dataset;
+ this.existingDatasetSelected = true;
+
+ /*let datasets = document.getElementsByClassName("usersDataset") as HTMLCollection;
+ for (let i = 0; i < datasets.length; i++) {
+ if (datasets[i]._id == dataset._id)
+ }*/
+
+ this.resetCbsAndRbs();
+ }
+
+ resetSelectedDataset(): boolean {
+ this.existingDatasetSelected = false;
+ this.selectedDataset = undefined;
+ return true;
+ }
+ resetCbsAndRbs(): boolean {
+ this.uncheckRbs();
+ this.checkAllCbs();
+ return true;
+ }
+ checkAllCbs() {
+ let checkboxes: any;
+ //if (this.showMyDatasets)
+ checkboxes = document.getElementsByName("cbsExisting");
+ //else
+ //checkboxes = document.getElementsByName("cbsNew");
+
+ for (let i = 0; i < checkboxes.length; i++) {
+ (<HTMLInputElement>checkboxes[i]).checked = true;
+ (<HTMLInputElement>checkboxes[i]).disabled = false;
+ }
+
+ checkboxes = document.getElementsByName("cbsNew");
+ for (let i = 0; i < checkboxes.length; i++) {
+ (<HTMLInputElement>checkboxes[i]).checked = true;
+ (<HTMLInputElement>checkboxes[i]).disabled = false;
+ }
+ }
+ uncheckRbs() {
+ this.selectedOutputColumnVal = '';
+ let radiobuttons: any;
+ //if (this.showMyDatasets)
+ radiobuttons = document.getElementsByName("rbsExisting");
+ //else
+ //radiobuttons = document.getElementsByName("rbsNew");
+
+ for (let i = 0; i < radiobuttons.length; i++)
+ (<HTMLInputElement>radiobuttons[i]).checked = false;
+ radiobuttons = document.getElementsByName("rbsNew");
+ for (let i = 0; i < radiobuttons.length; i++)
+ (<HTMLInputElement>radiobuttons[i]).checked = false;
+ }
+
+ refreshMyDatasetList() {
+ this.models.getMyDatasets().subscribe((datasets) => {
+ this.myDatasets = datasets;
+ });
+ }
+
}
diff --git a/frontend/src/app/_services/models.service.ts b/frontend/src/app/_services/models.service.ts
index 8299016b..30d63956 100644
--- a/frontend/src/app/_services/models.service.ts
+++ b/frontend/src/app/_services/models.service.ts
@@ -38,4 +38,8 @@ export class ModelsService {
trainModel(modelId: string): Observable<any> {
return this.http.post(`${API_SETTINGS.apiURL}/model/train`, modelId, { headers: this.authService.authHeader() });
}
+
+ getMyDatasets(): Observable<Dataset[]> {
+ return this.http.get<Dataset[]>(`${API_SETTINGS.apiURL}/dataset/mydatasets`, { headers: this.authService.authHeader() });//responsetype text da l treba??
+ }
}
diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html
index 35e5adb1..24828a06 100644
--- a/frontend/src/app/app.component.html
+++ b/frontend/src/app/app.component.html
@@ -1,4 +1,6 @@
<app-navbar></app-navbar>
<div class="container h-100">
<router-outlet></router-outlet>
-</div> \ No newline at end of file
+ <app-barchart></app-barchart>
+ <app-scatterchart></app-scatterchart>
+</div>
diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts
index 5aa405fb..7f076421 100644
--- a/frontend/src/app/app.module.ts
+++ b/frontend/src/app/app.module.ts
@@ -6,6 +6,8 @@ import { HttpClientModule } from '@angular/common/http';
import { MatSliderModule } from '@angular/material/slider';
import { MatIconModule } from '@angular/material/icon';
+import {NgChartsModule} from 'ng2-charts';
+
import { AppComponent } from './app.component';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { DatasetLoadComponent } from './_elements/dataset-load/dataset-load.component';
@@ -29,6 +31,8 @@ import { MyModelsComponent } from './_pages/my-models/my-models.component';
import { BrowseDatasetsComponent } from './_pages/browse-datasets/browse-datasets.component';
import { BrowsePredictorsComponent } from './_pages/browse-predictors/browse-predictors.component';
import { PredictComponent } from './_pages/predict/predict.component';
+import { ScatterchartComponent } from './scatterchart/scatterchart.component';
+import { BarchartComponent } from './barchart/barchart.component';
@NgModule({
declarations: [
@@ -49,7 +53,9 @@ import { PredictComponent } from './_pages/predict/predict.component';
MyModelsComponent,
BrowseDatasetsComponent,
BrowsePredictorsComponent,
- PredictComponent
+ PredictComponent,
+ ScatterchartComponent,
+ BarchartComponent
],
imports: [
BrowserModule,
@@ -61,7 +67,8 @@ import { PredictComponent } from './_pages/predict/predict.component';
MaterialModule,
ReactiveFormsModule,
MatSliderModule,
- MatIconModule
+ MatIconModule,
+ NgChartsModule
],
providers: [],
bootstrap: [AppComponent]
diff --git a/frontend/src/app/barchart/barchart.component.css b/frontend/src/app/barchart/barchart.component.css
new file mode 100644
index 00000000..c3634c9f
--- /dev/null
+++ b/frontend/src/app/barchart/barchart.component.css
@@ -0,0 +1,6 @@
+#divBarChart{
+ background-color: beige;
+ display: block;
+ width: 400px;
+ height: 200px;
+}
diff --git a/frontend/src/app/barchart/barchart.component.html b/frontend/src/app/barchart/barchart.component.html
new file mode 100644
index 00000000..48b7bd3e
--- /dev/null
+++ b/frontend/src/app/barchart/barchart.component.html
@@ -0,0 +1,4 @@
+<p>Bar chart:</p>
+<div id="divBarChart">
+ <canvas id="Barchart"> </canvas>
+</div> \ No newline at end of file
diff --git a/frontend/src/app/barchart/barchart.component.spec.ts b/frontend/src/app/barchart/barchart.component.spec.ts
new file mode 100644
index 00000000..8b346d1c
--- /dev/null
+++ b/frontend/src/app/barchart/barchart.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { BarchartComponent } from './barchart.component';
+
+describe('BarchartComponent', () => {
+ let component: BarchartComponent;
+ let fixture: ComponentFixture<BarchartComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ BarchartComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(BarchartComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/barchart/barchart.component.ts b/frontend/src/app/barchart/barchart.component.ts
new file mode 100644
index 00000000..def64b7d
--- /dev/null
+++ b/frontend/src/app/barchart/barchart.component.ts
@@ -0,0 +1,54 @@
+import { Component, OnInit } from '@angular/core';
+import {Chart} from 'node_modules/chart.js';
+
+@Component({
+ selector: 'app-barchart',
+ templateUrl: './barchart.component.html',
+ styleUrls: ['./barchart.component.css']
+})
+export class BarchartComponent implements OnInit {
+
+ constructor() { }
+
+ ngOnInit(){
+ const myChart = new Chart("Barchart", {
+ type: 'bar',
+ data: {
+ labels: ['Red', 'Blue', 'Yellow', 'Green', 'Purple', 'Orange'],
+ datasets: [{
+ label: 'Number of Votes',
+ data: [12, 19, 3, 5, 2, 3],
+ backgroundColor: [
+ 'rgba(255, 99, 132, 1)',
+ 'rgba(54, 162, 235, 1)',
+ 'rgba(255, 206, 86, 1)',
+ 'rgba(75, 192, 192, 1)',
+ 'rgba(153, 102, 255, 1)',
+ 'rgba(255, 159, 64, 1)'
+ ],
+ borderColor: [
+ 'rgba(255, 99, 132, 1)',
+ 'rgba(54, 162, 235, 1)',
+ 'rgba(255, 206, 86, 1)',
+ 'rgba(75, 192, 192, 1)',
+ 'rgba(153, 102, 255, 1)',
+ 'rgba(255, 159, 64, 1)'
+ ],
+ borderWidth: 1
+ }]
+ },
+ options: {
+ scales: {
+ y: {
+ beginAtZero: true
+ }
+ }
+ }
+
+
+ });
+
+
+ }
+
+}
diff --git a/frontend/src/app/scatterchart/scatterchart.component.css b/frontend/src/app/scatterchart/scatterchart.component.css
new file mode 100644
index 00000000..5735217e
--- /dev/null
+++ b/frontend/src/app/scatterchart/scatterchart.component.css
@@ -0,0 +1,6 @@
+#divScatterChart{
+ background-color: beige;
+ display: block;
+ width: 400px;
+ height: 200px;
+} \ No newline at end of file
diff --git a/frontend/src/app/scatterchart/scatterchart.component.html b/frontend/src/app/scatterchart/scatterchart.component.html
new file mode 100644
index 00000000..2b30fe1f
--- /dev/null
+++ b/frontend/src/app/scatterchart/scatterchart.component.html
@@ -0,0 +1,4 @@
+<p>Scatter chart:</p>
+<div id="divScatterChart">
+ <canvas id="ScatterCharts"> </canvas>
+</div> \ No newline at end of file
diff --git a/frontend/src/app/scatterchart/scatterchart.component.spec.ts b/frontend/src/app/scatterchart/scatterchart.component.spec.ts
new file mode 100644
index 00000000..1db81051
--- /dev/null
+++ b/frontend/src/app/scatterchart/scatterchart.component.spec.ts
@@ -0,0 +1,25 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ScatterchartComponent } from './scatterchart.component';
+
+describe('ScatterchartComponent', () => {
+ let component: ScatterchartComponent;
+ let fixture: ComponentFixture<ScatterchartComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [ ScatterchartComponent ]
+ })
+ .compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ScatterchartComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/scatterchart/scatterchart.component.ts b/frontend/src/app/scatterchart/scatterchart.component.ts
new file mode 100644
index 00000000..1da88fe7
--- /dev/null
+++ b/frontend/src/app/scatterchart/scatterchart.component.ts
@@ -0,0 +1,32 @@
+import { Component, OnInit } from '@angular/core';
+import {Chart} from 'node_modules/chart.js';
+
+@Component({
+ selector: 'app-scatterchart',
+ templateUrl: './scatterchart.component.html',
+ styleUrls: ['./scatterchart.component.css']
+})
+export class ScatterchartComponent implements OnInit {
+
+ constructor() { }
+
+ ngOnInit(){
+ const myChart = new Chart("ScatterCharts", {
+ type: 'scatter',
+ data: {
+ datasets: [{
+ label: 'Scatter Example:',
+ data: [{x: 1, y: 11}, {x:2, y:12}, {x: 1, y: 2}, {x: 2, y: 4}, {x: 3, y: 8},{x: 4, y: 16}, {x: 1, y: 3}, {x: 3, y: 4}, {x: 4, y: 6}, {x: 6, y: 9}],
+ backgroundColor: 'rgb(255, 99, 132)'
+ }]
+ },
+ options: {
+ scales: {
+ y: {
+ beginAtZero: true
+ }
+ }
+ }
+ });
+ }
+}
diff --git a/frontend/src/index.html b/frontend/src/index.html
index b3b6eb54..0079969e 100644
--- a/frontend/src/index.html
+++ b/frontend/src/index.html
@@ -12,5 +12,6 @@
</head>
<body class="mat-typography">
<app-root></app-root>
+ <script src="node_modules/chart.js/src/chart.js"></script>
</body>
</html>
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index d37ab6f1..5a30802b 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -1,3 +1,4 @@
+@import '~bootstrap/dist/css/bootstrap.min.css';
body {
background-image: url('/assets/images/add_model_background.jpg');
} \ No newline at end of file