diff options
author | Sonja Galovic <galovicsonja@gmail.com> | 2022-06-06 04:21:57 +0200 |
---|---|---|
committer | Sonja Galovic <galovicsonja@gmail.com> | 2022-06-06 04:21:57 +0200 |
commit | a5dedce9016f75de00954f02cdaf865a66454220 (patch) | |
tree | 9f8c4dd85c367fdd63af75dfb34b1a7a9e1ba5db | |
parent | 2b5200bc9d3c55ca2d3f44c7dda23568125540ff (diff) | |
parent | 67151f6cc5fcb9a66fc08a181eb8e1a6acaca733 (diff) |
Merge branch 'redesign' of http://gitlab.pmf.kg.ac.rs/igrannonica/neuronstellar into redesign
36 files changed, 479 insertions, 40 deletions
diff --git a/backend/api/api/Controllers/DatasetController.cs b/backend/api/api/Controllers/DatasetController.cs index c93ac9db..9a3c3d86 100644 --- a/backend/api/api/Controllers/DatasetController.cs +++ b/backend/api/api/Controllers/DatasetController.cs @@ -282,5 +282,37 @@ namespace api.Controllers return Ok($"Dataset with ID = {id} deleted"); } + + [HttpPut("UpdateAccessibleByLink/{datasetId}")] + [Authorize(Roles = "User")] + public ActionResult UpdateAccessibleByLink(string datasetId, [FromBody] bool accessibleByLink) + { + string uploaderId = getUserId(); + + Dataset dataset = _datasetService.GetOneDataset(datasetId); + + if (uploaderId != dataset.uploaderId) + return Unauthorized(); + + _datasetService.UpdateAccessibleByLink(datasetId, accessibleByLink); + + return Ok(dataset.accessibleByLink); + } + + [HttpPut("UpdateIsPublic/{datasetId}")] + [Authorize(Roles = "User")] + public ActionResult UpdateIsPublic(string datasetId, [FromBody] bool isPublic) + { + string uploaderId = getUserId(); + + Dataset dataset = _datasetService.GetOneDataset(datasetId); + + if (uploaderId != dataset.uploaderId) + return Unauthorized(); + + _datasetService.UpdateIsPublic(datasetId, isPublic); + + return Ok(dataset.isPublic); + } } }
\ No newline at end of file diff --git a/backend/api/api/Controllers/ModelController.cs b/backend/api/api/Controllers/ModelController.cs index c574de28..c7dfe89c 100644 --- a/backend/api/api/Controllers/ModelController.cs +++ b/backend/api/api/Controllers/ModelController.cs @@ -312,6 +312,38 @@ namespace api.Controllers } + [HttpPut("UpdateAccessibleByLink/{modelId}")] + [Authorize(Roles = "User")] + public ActionResult UpdateAccessibleByLink(string modelId, [FromBody] bool accessibleByLink) + { + string uploaderId = getUserId(); + + Model model = _modelService.GetOneModel(modelId); + + if (uploaderId != model.uploaderId) + return Unauthorized(); + + _modelService.UpdateAccessibleByLink(modelId, accessibleByLink); + + return Ok(model.accessibleByLink); + } + + [HttpPut("UpdateIsPublic/{modelId}")] + [Authorize(Roles = "User")] + public ActionResult UpdateIsPublic(string modelId, [FromBody] bool isPublic) + { + string uploaderId = getUserId(); + + Model model = _modelService.GetOneModel(modelId); + + if (uploaderId != model.uploaderId) + return Unauthorized(); + + _modelService.UpdateIsPublic(modelId, isPublic); + + return Ok(model.isPublic); + } + } public class TrainModelObject diff --git a/backend/api/api/Interfaces/IDatasetService.cs b/backend/api/api/Interfaces/IDatasetService.cs index 2f7d0010..5a91c82b 100644 --- a/backend/api/api/Interfaces/IDatasetService.cs +++ b/backend/api/api/Interfaces/IDatasetService.cs @@ -19,5 +19,8 @@ namespace api.Services public void Update(Dataset dataset); string GetDatasetId(string fileId); //bool CheckDb(); + + public void UpdateAccessibleByLink(string datasetId, bool accessibleByLink); + public void UpdateIsPublic(string datasetId, bool isPublic); } } diff --git a/backend/api/api/Interfaces/IModelService.cs b/backend/api/api/Interfaces/IModelService.cs index 41cd279c..949c7278 100644 --- a/backend/api/api/Interfaces/IModelService.cs +++ b/backend/api/api/Interfaces/IModelService.cs @@ -19,6 +19,9 @@ namespace api.Services void Delete(string userId, string name); bool CheckHyperparameters(int inputNeurons, int hiddenLayerNeurons, int hiddenLayers, int outputNeurons); bool CheckDb(); + + public void UpdateAccessibleByLink(string modelId, bool accessibleByLink); + public void UpdateIsPublic(string modelId, bool isPublic); } } diff --git a/backend/api/api/Services/DatasetService.cs b/backend/api/api/Services/DatasetService.cs index 0b84721e..cd27f275 100644 --- a/backend/api/api/Services/DatasetService.cs +++ b/backend/api/api/Services/DatasetService.cs @@ -75,7 +75,7 @@ namespace api.Services public Dataset GetOneDataset(string userId, string id) { - return _dataset.Find(dataset => dataset.uploaderId == userId && dataset._id == id && dataset.isPreProcess).FirstOrDefault(); + return _dataset.Find(dataset => (dataset.uploaderId == userId || dataset.isPublic || dataset.accessibleByLink) && dataset._id == id && dataset.isPreProcess).FirstOrDefault(); } public Dataset GetOneDatasetN(string userId, string name) { @@ -104,6 +104,22 @@ namespace api.Services return dataset._id; } - + + public void UpdateAccessibleByLink(string datasetId, bool accessibleByLink) + { + Dataset dataset = _dataset.Find(dataset => dataset._id == datasetId).FirstOrDefault(); + dataset.accessibleByLink = accessibleByLink; + + _dataset.ReplaceOne(dataset => dataset._id == datasetId, dataset); + } + + public void UpdateIsPublic(string datasetId, bool isPublic) + { + Dataset dataset = _dataset.Find(dataset => dataset._id == datasetId).FirstOrDefault(); + dataset.isPublic = isPublic; + + _dataset.ReplaceOne(dataset => dataset._id == datasetId, dataset); + } + } } diff --git a/backend/api/api/Services/FileService.cs b/backend/api/api/Services/FileService.cs index 6ef74ca1..e5b7945f 100644 --- a/backend/api/api/Services/FileService.cs +++ b/backend/api/api/Services/FileService.cs @@ -28,7 +28,7 @@ namespace api.Services public string GetFilePath(string id, string uploaderId) { FileModel file; - if (_dataset.Find(x=>x.fileId==id && x.isPublic==true).FirstOrDefault()!=null) + if (_dataset.Find(x=>x.fileId==id && (x.isPublic==true ||x.accessibleByLink==true)).FirstOrDefault()!=null) file = _file.Find(x => x._id == id).FirstOrDefault(); else file = _file.Find(x => x._id == id && x.uploaderId == uploaderId).FirstOrDefault(); diff --git a/backend/api/api/Services/ModelService.cs b/backend/api/api/Services/ModelService.cs index 71db6340..1c690ca7 100644 --- a/backend/api/api/Services/ModelService.cs +++ b/backend/api/api/Services/ModelService.cs @@ -106,5 +106,21 @@ namespace api.Services return true; } + + public void UpdateAccessibleByLink(string modelId, bool accessibleByLink) + { + Model model = _model.Find(model => model._id == modelId).FirstOrDefault(); + model.accessibleByLink = accessibleByLink; + + _model.ReplaceOne(model => model._id == modelId, model); + } + + public void UpdateIsPublic(string modelId, bool isPublic) + { + Model model = _model.Find(model => model._id == modelId).FirstOrDefault(); + model.isPublic = isPublic; + + _model.ReplaceOne(model => model._id == modelId, model); + } } } diff --git a/docs/SpecifikacijaDizajnaSoftvera.docx b/docs/SpecifikacijaDizajnaSoftvera.docx Binary files differnew file mode 100644 index 00000000..dfc5345b --- /dev/null +++ b/docs/SpecifikacijaDizajnaSoftvera.docx diff --git a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css index 862a86e1..60b341d0 100644 --- a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css +++ b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.css @@ -22,7 +22,8 @@ canvas { } .hide { - display: none; + display: none !important; + background-color: red; } .dl-button { diff --git a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.html b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.html index cc1c0121..505dd50b 100644 --- a/frontend/src/app/_elements/_charts/line-chart/line-chart.component.html +++ b/frontend/src/app/_elements/_charts/line-chart/line-chart.component.html @@ -1,5 +1,5 @@ <div #wrapper class="position-relative" style="width:100%;height:95%; border: 1px solid var(--ns-accent); background-color: var(--ns-bg-dark-100); border-radius: 15px;"> - <div class="d-flex flex-column align-items-stretch" [ngClass]="{'hide':experiment.type != ProblemType.Regression}" style="width: 100%; height: 90%;"> + <div class="d-flex flex-column align-items-stretch" [ngClass]="{'hide':experiment.type !== ProblemType.Regression}" style="width: 100%; height: 90%;"> <div class="canvas-container"> <canvas #myChartmae> </canvas> @@ -9,18 +9,16 @@ </canvas> </div> </div> - <div class="ns-row" [ngClass]="{'hide':experiment.type == ProblemType.Regression}"> - <div class="ns-col"> + <div class="d-flex flex-column align-items-stretch" [ngClass]="{'hide':experiment.type === ProblemType.Regression}" style="width: 100%; height: 90%;"> + <div class="canvas-container"> <canvas #myChartloss> </canvas> </div> - <div class="ns-col"> + <div class="canvas-container"> <canvas #myChartacc> </canvas> </div> </div> - - <div class="bottom d-flex flex-row" style="justify-content: space-between; align-items: center;"> <h3 class="mt-5 mx-2"><span *ngIf="modelName.length > 0 && predictor">Model treniran za konfiguraciju: {{modelName}}</span><span *ngIf="modelName.length > 0 && !predictor">Model se trenira za konfiguraciju: {{modelName}}</span></h3> <button *ngIf="predictor" class="mt-5 mx-2 btn-clear dl-button d-flex flex-row" (click)="downloadFile();" style="display: inline-block;" matTooltip="Preuzmi H5" matTooltipPosition="above"> diff --git a/frontend/src/app/_elements/folder/folder.component.html b/frontend/src/app/_elements/folder/folder.component.html index 5d5e7822..e77e05a3 100644 --- a/frontend/src/app/_elements/folder/folder.component.html +++ b/frontend/src/app/_elements/folder/folder.component.html @@ -76,6 +76,9 @@ {{file.lastUpdated | date}} </div> <div class="mx-2 hover-show" *ngIf="selectedTab !== TabType.PublicDatasets && selectedTab !== TabType.PublicModels"> + <button *ngIf="selectedTab==TabType.MyDatasets || selectedTab==TabType.MyModels" class="btn-clear file-button" (click)="shareFile(file,$event)" style="display: inline-block;" matTooltip="Podeli" matTooltipPosition="above"> + <mat-icon>share</mat-icon> + </button> <button *ngIf="selectedTab==TabType.MyDatasets" class="btn-clear file-button" (click)="downloadFile(file,$event)" style="display: inline-block;" matTooltip="Preuzmi" matTooltipPosition="above"> <mat-icon>download</mat-icon> </button> @@ -122,12 +125,15 @@ <div [ngSwitch]="newFileSelected" *ngIf="!listView"> <div class="file-bottom-buttons" *ngIf="selectedTab != TabType.NewFile"> <div class="file-bottom-buttons-helper"> - <button *ngIf="this.selectedFile && selectedTab == TabType.File && privacy != Privacy.Public" class="btn-clear file-button" (click)="deleteFile(this.selectedFile, $event)" matTooltip="Obriši" matTooltipPosition="above"> + <button *ngIf="this.selectedFile && selectedTab == TabType.File && privacy != Privacy.Public" class="btn-clear file-button" (click)="deleteFile(selectedFile, $event)" matTooltip="Obriši" matTooltipPosition="above"> <mat-icon>delete</mat-icon> </button> - <button *ngIf="this.selectedFile && selectedTab==TabType.File && FolderType.Dataset==this.type" class="btn-clear file-button" (click)="downloadFile(this.selectedFile,$event)" style="display: inline-block;" matTooltip="Preuzmi" matTooltipPosition="above"> + <button *ngIf="this.selectedFile && selectedTab==TabType.File && FolderType.Dataset==this.type" class="btn-clear file-button" (click)="downloadFile(selectedFile,$event)" style="display: inline-block;" matTooltip="Preuzmi" matTooltipPosition="above"> <mat-icon>download</mat-icon> </button> + <button *ngIf="this.selectedFile && selectedTab == TabType.File" class="btn-clear file-button" (click)="shareFile(selectedFile,$event)" style="display: inline-block;" matTooltip="Podeli" matTooltipPosition="above"> + <mat-icon>share</mat-icon> + </button> </div> <!-- <button class="btn-clear file-button"> <mat-icon>zoom_out_map</mat-icon> diff --git a/frontend/src/app/_elements/folder/folder.component.ts b/frontend/src/app/_elements/folder/folder.component.ts index 6eccb61b..5fa20859 100644 --- a/frontend/src/app/_elements/folder/folder.component.ts +++ b/frontend/src/app/_elements/folder/folder.component.ts @@ -15,6 +15,8 @@ import { ActivatedRoute, Router } from '@angular/router'; import Predictor from 'src/app/_data/Predictor'; import FileSaver from 'file-saver'; import isEqual from 'lodash.isequal'; +import { ShareDialogComponent } from 'src/app/_modals/share-dialog/share-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; @Component({ selector: 'app-folder', @@ -50,7 +52,7 @@ export class FolderComponent implements AfterViewInit { searchTerm: string = ''; - constructor(private datasetsService: DatasetsService, private experimentsService: ExperimentsService, private modelsService: ModelsService, private predictorsService: PredictorsService, private signalRService: SignalRService, private router: Router, private route: ActivatedRoute) { + constructor(private datasetsService: DatasetsService, private experimentsService: ExperimentsService, private modelsService: ModelsService, private predictorsService: PredictorsService, private signalRService: SignalRService, private router: Router, private route: ActivatedRoute, public dialog: MatDialog) { this.tabsToShow.forEach(tab => this.folders[tab] = []); } @@ -104,6 +106,18 @@ export class FolderComponent implements AfterViewInit { } selectFile(file?: FolderFile) { + if (this.privacy == Privacy.Public) { + if (file) { + if (this.type == FolderType.Dataset) { + this.router.navigate(['dataset', file?._id]); + } else if (this.type == FolderType.Model) { + this.router.navigate(['model', file?._id]); + } + } + + } + + this.formDataset.resetPagging(); this.selectedFile = file; this.updateLastFileData(file); @@ -154,6 +168,22 @@ export class FolderComponent implements AfterViewInit { this.okPressed.emit(); } + fileToShare: FolderFile | undefined = undefined; + + shareFile(file: FolderFile, event: Event) { + event.stopPropagation(); + + this.fileToShare = file; + + const dialogRef = this.dialog.open(ShareDialogComponent, { + width: '550px', + data: { file: this.fileToShare, fileType: this.type } + }); + dialogRef.afterClosed().subscribe(experiment => { + this.refreshFiles(); + }); + } + _initialized: boolean = false; refreshFiles(selectedDatasetId: string | null = null, selectedModelId: string | null = null) { @@ -547,9 +577,9 @@ export class FolderComponent implements AfterViewInit { selectTab(tab: TabType) { if (tab == TabType.NewFile) { this.selectNewFile(); - } else if (tab == TabType.File) { + } /*else if (tab == TabType.File) { this.selectFile(this.selectedFile); - } + }*/ this.listView = this.getListView(tab); this.type = this.getFolderType(tab); this.privacy = this.getPrivacy(tab); diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.html b/frontend/src/app/_elements/form-dataset/form-dataset.component.html index 2a956128..b518f15c 100644 --- a/frontend/src/app/_elements/form-dataset/form-dataset.component.html +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.html @@ -2,7 +2,7 @@ <div class="topBar"> <div class="kolona mb-3"> <div class="fileButton"> - <button type="button" mat-raised-button (click)="fileInput.click()" [disabled]="dataset._id != ''" > + <button type="button" mat-raised-button (click)="fileInput.click()" [disabled]="dataset._id != '' || disableAll"> <span *ngIf="!firstInput && dataset._id == '' ">Dodaj izvor podataka</span> <span *ngIf="firstInput && dataset._id == '' ">{{filename}}</span> <span *ngIf="dataset._id != '' ">Fajl je odabran</span> @@ -14,7 +14,7 @@ <div role="group"> <mat-form-field class="example-full-width" appearance="fill"> <mat-label>Naziv</mat-label> - <input type="text" matInput value="{{dataset?.name}}" [(ngModel)]="dataset.name" (input)="editEvent.emit()"> + <input type="text" matInput value="{{dataset?.name}}" [(ngModel)]="dataset.name" (input)="editEvent.emit()" [readonly]="disableAll"> <mat-error *ngIf="nameFormControl.hasError('required')"> @@ -26,7 +26,7 @@ <div class="kolona"> <mat-form-field appearance="fill"> <mat-label>Delimiter</mat-label> - <mat-select id="delimiterOptions" [(ngModel)]="dataset.delimiter" (selectionChange)="update(); editEvent.emit();" value=","> + <mat-select id="delimiterOptions" [(ngModel)]="dataset.delimiter" (selectionChange)="update(); editEvent.emit();" value="," [disabled]="disableAll"> <mat-option *ngFor="let option of delimiterOptions" [value]="option"> {{ option }} </mat-option> @@ -42,7 +42,7 @@ <i class="material-icons-outlined icon-display" [ngClass]="{'hidden': tableData.hasInput}">file_upload</i> - <input class="file" id="file-upload" [disabled]="dataset._id != ''" (change)="changeListener($event)" (valueChange)="dataset.isPreProcess = false; editEvent.emit()" #fileInput type="file" accept=".csv"> + <input class="file" id="file-upload" [disabled]="dataset._id != '' || disableAll" (change)="changeListener($event)" (valueChange)="dataset.isPreProcess = false; editEvent.emit()" #fileInput type="file" accept=".csv"> <div class="datatable"> <div [ngClass]="{'hidden': (!existingFlag)}" class="text-center"> diff --git a/frontend/src/app/_elements/form-dataset/form-dataset.component.ts b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts index 3df76aa5..3eb6fe39 100644 --- a/frontend/src/app/_elements/form-dataset/form-dataset.component.ts +++ b/frontend/src/app/_elements/form-dataset/form-dataset.component.ts @@ -13,6 +13,7 @@ import { FormControl, Validators } from '@angular/forms'; styleUrls: ['./form-dataset.component.css'] }) export class FormDatasetComponent { + @Input() disableAll: boolean = false; @ViewChild(DatatableComponent) datatable!: DatatableComponent; diff --git a/frontend/src/app/_elements/form-model/form-model.component.html b/frontend/src/app/_elements/form-model/form-model.component.html index 0d770fc1..1f0208fa 100644 --- a/frontend/src/app/_elements/form-model/form-model.component.html +++ b/frontend/src/app/_elements/form-model/form-model.component.html @@ -1,4 +1,4 @@ -<div *ngIf="newModel"> +<div *ngIf="newModel!=undefined"> <div id="container"> <div class="ns-row"> @@ -122,7 +122,8 @@ <div class="ns-col"> <!-- {{forExperiment._columnsSelected}} --> - <app-graph [model]="newModel" [inputColumns]="getInputColumns()"></app-graph> + + <app-graph [model]="newModel" [inputColumns]="getInputColumns()" [outputColumn]="(this.forExperiment!=undefined)?this.forExperiment.outputColumn:''"></app-graph> </div> </div> </div> diff --git a/frontend/src/app/_elements/form-model/form-model.component.ts b/frontend/src/app/_elements/form-model/form-model.component.ts index 7c84d2ba..7831f573 100644 --- a/frontend/src/app/_elements/form-model/form-model.component.ts +++ b/frontend/src/app/_elements/form-model/form-model.component.ts @@ -12,6 +12,8 @@ import { MatSliderChange } from '@angular/material/slider'; styleUrls: ['./form-model.component.css'] }) export class FormModelComponent implements AfterViewInit { + @Input() disableAll: boolean = false; + @ViewChild(GraphComponent) graph!: GraphComponent; @Input() forExperiment!: Experiment; @Output() selectedModelChangeEvent = new EventEmitter<Model>(); @@ -25,11 +27,7 @@ export class FormModelComponent implements AfterViewInit { @Output() editEvent = new EventEmitter(); ngAfterViewInit(): void { - this.lossFunction = this.lossFunctions[this.forProblemType][0]; - this.outputLayerActivationFunction = this.outputLayerActivationFunctions[this.forProblemType][0]; - - this.newModel.lossFunction = this.lossFunction; - this.newModel.outputLayerActivationFunction = this.outputLayerActivationFunction; + } selectFormControl = new FormControl('', Validators.required); @@ -80,10 +78,17 @@ export class FormModelComponent implements AfterViewInit { loadModel(model: Model) { this.newModel = model; this.forProblemType = model.type; + this.lossFunction = this.lossFunctions[this.forProblemType][0]; + this.outputLayerActivationFunction = this.outputLayerActivationFunctions[this.forProblemType][0]; + + this.newModel.lossFunction = this.lossFunction; + this.newModel.outputLayerActivationFunction = this.outputLayerActivationFunction; + this.updateGraph(); } updateGraph() { - this.graph.update(); + if(this.newModel) + this.graph.update(); } removeLayer() { diff --git a/frontend/src/app/_elements/graph/graph.component.css b/frontend/src/app/_elements/graph/graph.component.css index 456a8df1..f8604125 100644 --- a/frontend/src/app/_elements/graph/graph.component.css +++ b/frontend/src/app/_elements/graph/graph.component.css @@ -10,11 +10,14 @@ /*border: 1px solid red;*/ transform: translate(-50%, -50%); } - -.node-text:not(.inputs) { +.output{ + font-weight: bold; + color: var(--offwhite); +} +.node-text:not(.output) { color: transparent; } -.node-text:not(.inputs):hover { +.node-text:not(.output):hover { color: var(--offwhite); -}
\ No newline at end of file +} diff --git a/frontend/src/app/_elements/graph/graph.component.html b/frontend/src/app/_elements/graph/graph.component.html index 411e8b53..2ba42eba 100644 --- a/frontend/src/app/_elements/graph/graph.component.html +++ b/frontend/src/app/_elements/graph/graph.component.html @@ -1,8 +1,10 @@ <div #graphWrapper class="position-relative" style="height: 14rem; width: 100%;"> - <!-- <ng-container *ngFor="let layer of layers; let i = index"> - <div [ngClass]="{'inputs': i==0}" class="node-text" *ngFor="let node of layer; let j = index" [style.left.%]="node.x * 99.4" [style.top.%]="node.y * 100"> - {{ i == 0 ? (inputColumns && inputColumns.length >= j ? inputColumns[j] : 'nepoznato') : (i > 0 && i - < layers.length - 1 ? model!.layers[i-1].activationFunction : (i==layers.length - 1 ? 'out' : '')) }} </div> - </ng-container> --> + <div *ngIf="outputColumn!='' && inputColumns!=[]"> + <ng-container *ngFor="let layer of layers; let i = index"> + <div [ngClass]="{'inputs': i==0,'output':i==layers.length-1}" class="node-text" *ngFor="let node of layer; let j = index" [style.left.%]="node.x * 100" [style.top.%]="(i!=layers.length-1)?node.y * 100:node.y*70"> + {{ i == 0 ? (inputColumns && inputColumns.length >= j ? inputColumns[j] : 'nepoznato') : (i > 0 && i + < layers.length - 1 ? model!.layers[i-1].activationFunction : (i==layers.length - 1 ? " "+outputColumn : '')) }} </div> + </ng-container> + </div> <canvas #graphCanvas></canvas> </div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/graph/graph.component.ts b/frontend/src/app/_elements/graph/graph.component.ts index 04be9f04..f14e54e7 100644 --- a/frontend/src/app/_elements/graph/graph.component.ts +++ b/frontend/src/app/_elements/graph/graph.component.ts @@ -28,7 +28,8 @@ export class GraphComponent implements AfterViewInit { @Input() outputNodeColor: string = '#dfd7d7'; private ctx!: CanvasRenderingContext2D; - @Input() inputColumns?: string[]; + @Input() inputColumns?: string[]=[]; + @Input() outputColumn?:string=""; constructor() { } @@ -81,7 +82,7 @@ export class GraphComponent implements AfterViewInit { this.layers.push([new Node(outX, outY, this.outputNodeColor)]) this.draw(); } - + draw() { this.ctx.clearRect(0, 0, this.canvas.nativeElement.width, this.canvas.nativeElement.height); diff --git a/frontend/src/app/_modals/share-dialog/share-dialog.component.css b/frontend/src/app/_modals/share-dialog/share-dialog.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_modals/share-dialog/share-dialog.component.css diff --git a/frontend/src/app/_modals/share-dialog/share-dialog.component.html b/frontend/src/app/_modals/share-dialog/share-dialog.component.html new file mode 100644 index 00000000..32584197 --- /dev/null +++ b/frontend/src/app/_modals/share-dialog/share-dialog.component.html @@ -0,0 +1,14 @@ +<h1 mat-dialog-title class="text-center">Podeli {{data.fileType == 0 ? 'izvor podataka' : 'konfiguraciju neuronske mreže'}}</h1> +<div mat-dialog-content class="mt-5 mb-3 mx-1"> + <form> + <mat-checkbox class="m-2" [(ngModel)]="data.file.isPublic" [ngModelOptions]="{standalone: true}" (change)="publicChanged()">Svi mogu da vide </mat-checkbox> + <mat-checkbox class="m-2" [(ngModel)]="data.file.accessibleByLink" [ngModelOptions]="{standalone: true}" (change)="linkChanged()">Osobe sa linkom mogu da vide</mat-checkbox> + <div class="input-group my-3"> + <input type="text" [value]="link" class="form-control" placeholder="" aria-label="Copy link button addon" aria-describedby="button-copy"> + <button class="btn btn-outline-primary" type="button" id="button-copy" (click)="copy()" [disabled]="!data.file.accessibleByLink"><div class="mt-1"><mat-icon *ngIf="data.file.accessibleByLink">link</mat-icon><mat-icon *ngIf="!data.file.accessibleByLink">link_off</mat-icon></div></button> + </div> + </form> +</div> +<div mat-dialog-actions class="justify-content-center"> + <button mat-stroked-button (click)="close()">Gotovo</button> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_modals/share-dialog/share-dialog.component.spec.ts b/frontend/src/app/_modals/share-dialog/share-dialog.component.spec.ts new file mode 100644 index 00000000..51bd4c9a --- /dev/null +++ b/frontend/src/app/_modals/share-dialog/share-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ShareDialogComponent } from './share-dialog.component'; + +describe('ShareDialogComponent', () => { + let component: ShareDialogComponent; + let fixture: ComponentFixture<ShareDialogComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ShareDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ShareDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_modals/share-dialog/share-dialog.component.ts b/frontend/src/app/_modals/share-dialog/share-dialog.component.ts new file mode 100644 index 00000000..2331cd8b --- /dev/null +++ b/frontend/src/app/_modals/share-dialog/share-dialog.component.ts @@ -0,0 +1,70 @@ +import { Component, Inject, Input, OnInit } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import Dataset from 'src/app/_data/Dataset'; +import { FolderType } from 'src/app/_data/FolderFile'; +import Model from 'src/app/_data/Model'; +import { DatasetsService } from 'src/app/_services/datasets.service'; +import { ModelsService } from 'src/app/_services/models.service'; + +interface DialogData { + file: (Dataset | Model); + fileType: FolderType; +} + +@Component({ + selector: 'app-share-dialog', + templateUrl: './share-dialog.component.html', + styleUrls: ['./share-dialog.component.css'] +}) +export class ShareDialogComponent implements OnInit { + constructor(public dialogRef: MatDialogRef<ShareDialogComponent>, @Inject(MAT_DIALOG_DATA) public data: DialogData, private modelsService: ModelsService, private datasetsService: DatasetsService) { + + } + + link: string = ''; + + ngOnInit(): void { + let link = window.location.origin; + if (this.data.fileType == FolderType.Dataset) { + link += '/dataset/'; + } else if (this.data.fileType == FolderType.Model) { + link += '/model/'; + } + link += this.data.file._id; + this.link = link; + } + + close() { + this.dialogRef.close(); + } + + copy() { + navigator.clipboard.writeText(this.link); + } + + publicChanged() { + if (this.data.fileType == FolderType.Dataset) { + this.datasetsService.updateDatasetIsPublic(this.data.file._id, this.data.file.isPublic).subscribe(() => { + }); + } else if (this.data.fileType == FolderType.Model) { + this.modelsService.updateModelIsPublic(this.data.file._id, this.data.file.isPublic).subscribe(() => { + }); + } + + if (this.data.file.isPublic) { + this.data.file.accessibleByLink = true; + } + } + + linkChanged() { + if (this.data.fileType == FolderType.Dataset) { + this.datasetsService.updateDatasetAccessibleByLink(this.data.file._id, this.data.file.accessibleByLink).subscribe(() => { + }); + } else if (this.data.fileType == FolderType.Model) { + this.modelsService.updateModelAccessibleByLink(this.data.file._id, this.data.file.accessibleByLink).subscribe(() => { + }); + } + } + + FolderType = FolderType; +} diff --git a/frontend/src/app/_pages/page-dataset/page-dataset.component.css b/frontend/src/app/_pages/page-dataset/page-dataset.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/page-dataset/page-dataset.component.css diff --git a/frontend/src/app/_pages/page-dataset/page-dataset.component.html b/frontend/src/app/_pages/page-dataset/page-dataset.component.html new file mode 100644 index 00000000..2357a656 --- /dev/null +++ b/frontend/src/app/_pages/page-dataset/page-dataset.component.html @@ -0,0 +1,4 @@ +<div class="force-centered" style="color: var(--offwhite);"> + <app-form-dataset [disableAll]="true"></app-form-dataset> + <button mat-raised-button class="m-3 p-2" (click)="import()">Uvezi</button> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/page-dataset/page-dataset.component.spec.ts b/frontend/src/app/_pages/page-dataset/page-dataset.component.spec.ts new file mode 100644 index 00000000..2c961cd5 --- /dev/null +++ b/frontend/src/app/_pages/page-dataset/page-dataset.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageDatasetComponent } from './page-dataset.component'; + +describe('PageDatasetComponent', () => { + let component: PageDatasetComponent; + let fixture: ComponentFixture<PageDatasetComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageDatasetComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageDatasetComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/page-dataset/page-dataset.component.ts b/frontend/src/app/_pages/page-dataset/page-dataset.component.ts new file mode 100644 index 00000000..b6d0c2a4 --- /dev/null +++ b/frontend/src/app/_pages/page-dataset/page-dataset.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import Shared from 'src/app/Shared'; +import Dataset from 'src/app/_data/Dataset'; +import { FormDatasetComponent } from 'src/app/_elements/form-dataset/form-dataset.component'; +import { DatasetsService } from 'src/app/_services/datasets.service'; + +@Component({ + selector: 'app-page-dataset', + templateUrl: './page-dataset.component.html', + styleUrls: ['./page-dataset.component.css'] +}) +export class PageDatasetComponent implements OnInit { + + @ViewChild(FormDatasetComponent) formDataset!: FormDatasetComponent; + + constructor(private route: ActivatedRoute, private router: Router, private datasetsService: DatasetsService) { } + + ngOnInit(): void { + this.route.queryParams.subscribe(params => { + let id = this.route.snapshot.paramMap.get("id"); + if (id) { + this.datasetsService.getDatasetById(id).subscribe((dataset) => { + this.formDataset.dataset = dataset; + this.formDataset.loadExisting(); + }); + } else { + this.router.navigate(['']); + } + }); + } + + import() { + this.formDataset.dataset._id = ""; + this.formDataset.dataset.isPreProcess = true; + this.formDataset.dataset.isPublic = false; + this.datasetsService.stealDataset(this.formDataset.dataset).subscribe((response) => { + Shared.openDialog("Obaveštenje", "Uspešno ste dodali javni izvor podataka u vašu kolekciju."); + }, (error: any) => { + if (error.error == "Dataset with this name already exists") { + Shared.openDialog("Obaveštenje", "Izvor podataka sa ovim imenom postoji u vašoj kolekciji."); + } + }); + } +} diff --git a/frontend/src/app/_pages/page-model/page-model.component.css b/frontend/src/app/_pages/page-model/page-model.component.css new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/frontend/src/app/_pages/page-model/page-model.component.css diff --git a/frontend/src/app/_pages/page-model/page-model.component.html b/frontend/src/app/_pages/page-model/page-model.component.html new file mode 100644 index 00000000..e494542f --- /dev/null +++ b/frontend/src/app/_pages/page-model/page-model.component.html @@ -0,0 +1,4 @@ +<div class="force-centered" style="color: var(--offwhite);"> + <app-form-model [disableAll]="true"></app-form-model> + <button mat-raised-button class="m-3 p-2" (click)="import()">Uvezi</button> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_pages/page-model/page-model.component.spec.ts b/frontend/src/app/_pages/page-model/page-model.component.spec.ts new file mode 100644 index 00000000..e235de5c --- /dev/null +++ b/frontend/src/app/_pages/page-model/page-model.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PageModelComponent } from './page-model.component'; + +describe('PageModelComponent', () => { + let component: PageModelComponent; + let fixture: ComponentFixture<PageModelComponent>; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ PageModelComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(PageModelComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/_pages/page-model/page-model.component.ts b/frontend/src/app/_pages/page-model/page-model.component.ts new file mode 100644 index 00000000..f988ef0d --- /dev/null +++ b/frontend/src/app/_pages/page-model/page-model.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import Shared from 'src/app/Shared'; +import Model from 'src/app/_data/Model'; +import { FormModelComponent } from 'src/app/_elements/form-model/form-model.component'; +import { ModelsService } from 'src/app/_services/models.service'; + +@Component({ + selector: 'app-page-model', + templateUrl: './page-model.component.html', + styleUrls: ['./page-model.component.css'] +}) +export class PageModelComponent implements OnInit { + + @ViewChild(FormModelComponent) formModel!: FormModelComponent; + + constructor(private route: ActivatedRoute, private router: Router, private modelsService: ModelsService) { } + + ngOnInit(): void { + this.route.queryParams.subscribe(params => { + let id = this.route.snapshot.paramMap.get("id"); + if (id) { + this.modelsService.getModelById(id).subscribe((model) => { + this.formModel.newModel = model; + }); + } else { + this.router.navigate(['']); + } + }); + } + + import() { + this.formModel.newModel._id = ""; + this.formModel.newModel.isPublic = false; + this.modelsService.stealModel(this.formModel.newModel).subscribe((response) => { + Shared.openDialog("Obaveštenje", "Uspešno ste dodali javnu konfiguraciju neuronske mreže u vašu kolekciju."); + }, (error: any) => { + Shared.openDialog("Obaveštenje", "Konfiguracija neuronske mreže sa ovim imenom postoji u vašoj kolekciji."); + }); + } +} diff --git a/frontend/src/app/_services/datasets.service.ts b/frontend/src/app/_services/datasets.service.ts index 42fa5e55..11838b1d 100644 --- a/frontend/src/app/_services/datasets.service.ts +++ b/frontend/src/app/_services/datasets.service.ts @@ -70,4 +70,12 @@ export class DatasetsService { downloadFile(id: string): Observable<Blob> { return this.http.get(`${Configuration.settings.apiURL}/file/Download?id=` + id, { headers: this.authService.authHeader(), responseType: 'blob' }); } + + updateDatasetAccessibleByLink(id: string, acessibleByLink: boolean) { + return this.http.put(`${Configuration.settings.apiURL}/dataset/updateAccessibleByLink/` + id, acessibleByLink, { headers: this.authService.authHeader(), responseType: 'text' }); + } + + updateDatasetIsPublic(id: string, isPublic: boolean) { + return this.http.put(`${Configuration.settings.apiURL}/dataset/updateIsPublic/` + id, isPublic, { headers: this.authService.authHeader(), responseType: 'text' }); + } } diff --git a/frontend/src/app/_services/models.service.ts b/frontend/src/app/_services/models.service.ts index 016fa9bc..398c7813 100644 --- a/frontend/src/app/_services/models.service.ts +++ b/frontend/src/app/_services/models.service.ts @@ -51,4 +51,12 @@ export class ModelsService { return this.http.get<Model>(`${Configuration.settings.apiURL}/model/byid/${modelId}`, { headers: this.authService.authHeader() }); } + updateModelAccessibleByLink(id: string, acessibleByLink: boolean) { + return this.http.put(`${Configuration.settings.apiURL}/model/updateAccessibleByLink/` + id, acessibleByLink, { headers: this.authService.authHeader(), responseType: 'text' }); + } + + updateModelIsPublic(id: string, isPublic: boolean) { + return this.http.put(`${Configuration.settings.apiURL}/model/updateIsPublic/` + id, isPublic, { headers: this.authService.authHeader(), responseType: 'text' }); + } + } diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 507089a8..fa53d5f0 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -9,6 +9,8 @@ import { ExperimentComponent } from './_pages/experiment/experiment.component'; import { ArchiveComponent } from './_pages/archive/archive.component'; import { ColumnTableComponent } from './_elements/column-table/column-table.component'; import { TestComponent } from './_pages/test/test.component'; +import { PageDatasetComponent } from './_pages/page-dataset/page-dataset.component'; +import { PageModelComponent } from './_pages/page-model/page-model.component'; const routes: Routes = [ { path: '', component: HomeComponent, data: { title: 'Početna strana' } }, @@ -19,6 +21,8 @@ const routes: Routes = [ { path: 'profile', component: ProfileComponent, canActivate: [AuthGuardService], data: { title: 'Profil' } }, { path: 'playground', component: PlaygroundComponent, data: { title: 'Zabava' } }, { path: 'test', component: TestComponent, data: { title: 'Test' } }, + { path: 'dataset/:id', component: PageDatasetComponent, data: { title: 'Izvor podataka' } }, + { path: 'model/:id', component: PageModelComponent, data: { title: 'Konfiguracija neuronske mreže' } }, { path: '**', redirectTo: '' } ]; diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index e2823761..21b09318 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -54,6 +54,9 @@ import { HeatmapComponent } from './_elements/_charts/heatmap/heatmap.component' import { HeatMapAllModule } from '@syncfusion/ej2-angular-heatmap'; import { MetricViewComponent } from './_elements/metric-view/metric-view.component'; import { SpinnerComponent } from './_elements/spinner/spinner.component'; +import { PageDatasetComponent } from './_pages/page-dataset/page-dataset.component'; +import { PageModelComponent } from './_pages/page-model/page-model.component'; +import { ShareDialogComponent } from './_modals/share-dialog/share-dialog.component'; export function initializeApp(appConfig: Configuration) { return () => appConfig.load(); @@ -95,7 +98,10 @@ export function initializeApp(appConfig: Configuration) { LineChartComponent, SaveExperimentDialogComponent, SpinnerComponent, - UpdateExperimentDialogComponent + UpdateExperimentDialogComponent, + PageDatasetComponent, + PageModelComponent, + ShareDialogComponent ], imports: [ BrowserModule, diff --git a/frontend/src/styles/layout.css b/frontend/src/styles/layout.css index 07c0bf34..790d2ca7 100644 --- a/frontend/src/styles/layout.css +++ b/frontend/src/styles/layout.css @@ -66,4 +66,14 @@ body { .break-2 { display: none; } -}*/
\ No newline at end of file +}*/ + +.force-centered { + width: 80%; + height: 80%; + margin: auto; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +}
\ No newline at end of file |