From baf8d13676a13e835dc1db007aeb3233d5c00fa5 Mon Sep 17 00:00:00 2001 From: uday biswas Date: Mon, 14 Jul 2025 00:55:59 +0530 Subject: [PATCH 1/3] implemented dataset and notebook opening , different admin login page --- .../src/app/admin/admin-routing.module.ts | 8 +- .../ClientApp/src/app/admin/admin.module.ts | 16 +- .../admin-login/admin-login.component.html | 31 +++ .../admin-login/admin-login.component.scss | 41 ++++ .../admin-login/admin-login.component.spec.ts | 23 ++ .../admin-login/admin-login.component.ts | 24 ++ .../datasets/datasets.component.html | 69 +++--- .../components/datasets/datasets.component.ts | 44 +++- ...admin-notebook-item-display.component.html | 26 ++ ...admin-notebook-item-display.component.scss | 41 ++++ ...in-notebook-item-display.component.spec.ts | 23 ++ .../admin-notebook-item-display.component.ts | 229 ++++++++++++++++++ .../forageIndexDb.ts | 49 ++++ .../admin-notebook-item.component.html | 53 +++- .../admin-notebook-item.component.ts | 112 ++++++++- .../notebooks/notebooks.component.html | 6 +- .../notebooks/notebooks.component.ts | 56 ++++- .../save-confirmation-modal.component.html | 9 + .../save-confirmation-modal.component.scss | 78 ++++++ .../save-confirmation-modal.component.spec.ts | 23 ++ .../save-confirmation-modal.component.ts | 23 ++ .../save-notebook-modal.component.html | 12 + .../save-notebook-modal.component.scss | 78 ++++++ .../save-notebook-modal.component.spec.ts | 23 ++ .../save-notebook-modal.component.ts | 26 ++ .../src/app/admin/guards/admin-auth.guard.ts | 14 ++ .../src/app/projects/projects.module.ts | 3 + 27 files changed, 1091 insertions(+), 49 deletions(-) create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.html create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.scss create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.spec.ts create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.ts create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.html create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.scss create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.spec.ts create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.ts create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/forageIndexDb.ts create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.html create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.scss create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.spec.ts create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.ts create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.html create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.scss create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.spec.ts create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.ts create mode 100644 src/Analysim.Web/ClientApp/src/app/admin/guards/admin-auth.guard.ts diff --git a/src/Analysim.Web/ClientApp/src/app/admin/admin-routing.module.ts b/src/Analysim.Web/ClientApp/src/app/admin/admin-routing.module.ts index 8c21719..3f4acd0 100644 --- a/src/Analysim.Web/ClientApp/src/app/admin/admin-routing.module.ts +++ b/src/Analysim.Web/ClientApp/src/app/admin/admin-routing.module.ts @@ -1,18 +1,22 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { AdminComponent } from './admin.component'; -import { AuthGuardService } from '../guards/auth-guard.service'; +import { AdminLoginComponent } from './components/admin-login/admin-login.component'; +import { AdminAuthGuard } from './guards/admin-auth.guard'; import { NotebooksComponent } from './components/notebooks/notebooks.component'; import { UsersComponent } from './components/users/users.component'; import { DatasetsComponent } from './components/datasets/datasets.component'; import { ProjectsComponent } from './components/projects/projects.component'; const routes: Routes = [ + { path: 'login', component: AdminLoginComponent }, + { path: '', component: AdminComponent, - canActivate: [AuthGuardService], + canActivate: [AdminAuthGuard], children: [ + { path: 'notebooks/:notebookRoute', component: NotebooksComponent }, { path: 'notebooks', component: NotebooksComponent }, { path: 'users', component: UsersComponent }, { path: 'datasets', component: DatasetsComponent }, diff --git a/src/Analysim.Web/ClientApp/src/app/admin/admin.module.ts b/src/Analysim.Web/ClientApp/src/app/admin/admin.module.ts index d89d13d..cb56d3c 100644 --- a/src/Analysim.Web/ClientApp/src/app/admin/admin.module.ts +++ b/src/Analysim.Web/ClientApp/src/app/admin/admin.module.ts @@ -6,21 +6,33 @@ import { AdminComponent } from './admin.component'; import { NotebooksComponent } from './components/notebooks/notebooks.component'; import { UsersComponent } from './components/users/users.component'; import { DatasetsComponent } from './components/datasets/datasets.component'; +import { FormsModule } from '@angular/forms'; +import { SaveConfirmationModalComponent } from './components/notebooks/save-confirmation-modal/save-confirmation-modal.component'; +import { SaveNotebookModalComponent } from './components/notebooks/save-notebook-modal/save-notebook-modal.component'; import { ProjectsComponent } from './components/projects/projects.component'; import { AdminNotebookItemComponent } from './components/notebooks/admin-notebook-item/admin-notebook-item.component'; +import { AdminNotebookItemDisplayComponent } from './components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component'; +import { ProjectsModule } from '../projects/projects.module'; +import { AdminLoginComponent } from './components/admin-login/admin-login.component'; @NgModule({ declarations: [ AdminComponent, NotebooksComponent, UsersComponent, + SaveConfirmationModalComponent, + SaveNotebookModalComponent, DatasetsComponent, ProjectsComponent, - AdminNotebookItemComponent + AdminNotebookItemComponent, + AdminNotebookItemDisplayComponent, + AdminLoginComponent ], imports: [ CommonModule, - AdminRoutingModule + AdminRoutingModule, + ProjectsModule, + FormsModule ] }) export class AdminModule { } diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.html b/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.html new file mode 100644 index 0000000..93323d9 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.html @@ -0,0 +1,31 @@ +
+

Admin Login

+
+
+ + +
+ +
+ + +
+ + + +
{{ error }}
+
+
diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.scss b/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.scss new file mode 100644 index 0000000..2cbeb45 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.scss @@ -0,0 +1,41 @@ +.admin-login { + max-width: 320px; + margin: 80px auto; + padding: 20px; + background: var(--background-color); + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + + h2 { + text-align: center; + margin-bottom: 1rem; + font-size: 1.5rem; + color: var(--primary-color); + } + + .form-group { + margin-bottom: 1rem; + + label { + display: block; + margin-bottom: 0.5rem; + color: var(--text-color); + } + + .form-control { + width: 100%; + padding: 0.5rem; + border: 1px solid var(--border-color); + border-radius: 4px; + } + } + + .btn { + width: 100%; + } + + .error { + color: #dc3545; + text-align: center; + } +} diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.spec.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.spec.ts new file mode 100644 index 0000000..cf91e2b --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminLoginComponent } from './admin-login.component'; + +describe('AdminLoginComponent', () => { + let component: AdminLoginComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminLoginComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AdminLoginComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.ts new file mode 100644 index 0000000..39a9cd4 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.ts @@ -0,0 +1,24 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-admin-login', + templateUrl: './admin-login.component.html', + styleUrls: ['./admin-login.component.scss'] +}) +export class AdminLoginComponent { + username = ''; + password = ''; + error: string | null = null; + + constructor(private router: Router) {} + + login() { + if (this.username === 'ADMIN' && this.password === 'ADMIN') { + localStorage.setItem('adminLoggedIn', 'true'); + this.router.navigateByUrl('/admin'); + } else { + this.error = 'Invalid credentials'; + } + } +} diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/datasets/datasets.component.html b/src/Analysim.Web/ClientApp/src/app/admin/components/datasets/datasets.component.html index 99d74ff..fc4968c 100644 --- a/src/Analysim.Web/ClientApp/src/app/admin/components/datasets/datasets.component.html +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/datasets/datasets.component.html @@ -1,31 +1,42 @@ - -
-
-
All Datasets
-
-
- - - - - - - - - - - - - - - - - -
NameDirectorySizeDate Created
{{d.name}}{{d.extension}}{{d.directory || '/'}}{{d.size | number}} bytes{{d.dateCreated | date:'short'}}
+ +
+

All Datasets

+
Loading…
+
{{ error }}
-
-
Loading… -
-
{{ error }}
-
+ + + + + + + + + + + + + + + + + +
NameDirectorySize (bytes)
{{ ds.name + ds.extension }}{{ ds.directory }}{{ ds.size }} + +
+ + + + + diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/datasets/datasets.component.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/datasets/datasets.component.ts index fd18c30..18f48c1 100644 --- a/src/Analysim.Web/ClientApp/src/app/admin/components/datasets/datasets.component.ts +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/datasets/datasets.component.ts @@ -1,6 +1,8 @@ -import { Component, OnInit } from '@angular/core'; +// src/app/admin/components/datasets/admin-datasets.component.ts +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; +import { BlobFile } from 'src/app/interfaces/blob-file'; import { ProjectService } from 'src/app/services/project.service'; -import { BlobFile } from '../../../interfaces/blob-file'; @Component({ selector: 'app-datasets', @@ -9,21 +11,43 @@ import { BlobFile } from '../../../interfaces/blob-file'; }) export class DatasetsComponent implements OnInit { datasets: BlobFile[] = []; - loading = true; - error: string = null; + loading = false; + error: string | null = null; - constructor(private project: ProjectService) { } + selectedDataset: BlobFile | null = null; + @ViewChild('previewModal') previewModal!: TemplateRef; + previewModalRef!: BsModalRef; + + constructor( + private projectService: ProjectService, + private modalService: BsModalService + ) {} ngOnInit() { - this.project.getAllDatasets().subscribe({ - next: result => { - this.datasets = result; + this.loadDatasets(); + } + + loadDatasets() { + this.loading = true; + this.error = null; + this.projectService.getAllDatasets().subscribe({ + next: list => { + this.datasets = list; this.loading = false; }, - error: () => { - this.error = 'Failed to load datasets'; + error: e => { + this.error = e.message || 'Failed to load datasets'; this.loading = false; } }); } + + preview(ds: BlobFile) { + this.selectedDataset = ds; + this.previewModalRef = this.modalService.show(this.previewModal, { class: 'modal-lg' }); + } + + closePreview() { + this.previewModalRef.hide(); + } } diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.html b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.html new file mode 100644 index 0000000..e735a5b --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.html @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.scss b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.scss new file mode 100644 index 0000000..edbc2d8 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.scss @@ -0,0 +1,41 @@ +.card-header { + background-color: var(--primary-color); + font-size: 18px; + font-weight: 500; +} + +.full-screen-modal { + max-width: 100%; + margin: 0; +} + +.spinner-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + gap: 20px; +} + +.spinner-border { + width: 3rem; + height: 3rem; +} + +.header { + display: flex; + justify-content: space-between; + align-items: center; + margin: 9px; +} + +.commit { + width: max-content; + background-color: rgb(81, 81, 223); + color: white; + border-radius: 5px; + padding: 5px 10px; + cursor: pointer; + font-size: 15px; +} \ No newline at end of file diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.spec.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.spec.ts new file mode 100644 index 0000000..8f7e544 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AdminNotebookItemDisplayComponent } from './admin-notebook-item-display.component'; + +describe('AdminNotebookItemDisplayComponent', () => { + let component: AdminNotebookItemDisplayComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AdminNotebookItemDisplayComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AdminNotebookItemDisplayComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.ts new file mode 100644 index 0000000..c7ec771 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component.ts @@ -0,0 +1,229 @@ +import { Component, ElementRef, EventEmitter, Input, OnInit, Output, Renderer2, ViewChild } from '@angular/core'; +import { Notebook, NotebookFile } from '../../../../../../interfaces/notebook'; +import { ProjectService } from '../../../../../../services/project.service'; +import { HttpClient } from '@angular/common/http'; +import { JupyterLiteStorageService } from '../forageIndexDb'; +import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; + +@Component({ + selector: 'app-admin-notebook-item-display', + templateUrl: './admin-notebook-item-display.component.html', + styleUrls: ['./admin-notebook-item-display.component.scss'] +}) +export class AdminNotebookItemDisplayComponent { + + + @Input() notebook: Notebook; + @Input() version: number; + + @Output() closeModal: EventEmitter = new EventEmitter(); + showSaveWarningModal = false; + showSaveNotebookModal = false; + + constructor(private projectService: ProjectService, private _renderer2: Renderer2, private http: HttpClient + , private sanitizer: DomSanitizer, private jupyterLiteStorageService: JupyterLiteStorageService + ) { } + + @ViewChild('observablehqPanel', { read: ElementRef }) observablehqPanel; + @ViewChild('jupyterFrame') jupyterFrame: ElementRef; + @ViewChild('notebookWindow') notebookWindow: ElementRef; + jupyterFrameSrc: SafeResourceUrl; + isLoading = true; + timeoutId: any; + notebookFile: NotebookFile; + commitChangesLoading = false; + + ngOnInit(): void { + window.addEventListener('message', this.receiveMessage.bind(this)); + if (this.notebook.type === 'notebook' || this.notebook.type === 'new') { + this.loadNotebook(); + } + this.setTimeoutForLoading(); + } + + ngAfterViewInit(): void { + if (this.notebook.type === 'observable') { + this.isLoading = false; + this.generateObservableNotebook(); + } + } + + setTimeoutForLoading(): void { + this.timeoutId = setTimeout(() => { + if (this.isLoading) { + this.isLoading = false; + this.closeModal.emit(); + console.log("some unknown error occurred , please open the notebook again."); + alert("some unknown error occurred , please open the notebook again."); + } + }, 30000); + } + + loadNotebook() { + const url = `../../../../../../../assets/jupyter/dist/lab/index.html?path=${this.notebook.name}${this.notebook.extension}`; + this.jupyterFrameSrc = this.sanitizer.bypassSecurityTrustResourceUrl(url); + + this.projectService.getNotebookFile(this.notebook, this.version) + .subscribe(nbContent => { + const notebookName = `${this.notebook.name}${this.notebook.extension}`; + const notebookData = { + content: nbContent, // The content of the notebook + created: new Date().toISOString(), + format: "json", + hash: null, + hash_algorithm: null, + last_modified: new Date().toISOString(), + mimetype: 'application/x-ipynb+json', + name: notebookName, + size: this.notebook.size, + path: notebookName, + type: 'notebook', + writable: true, + }; + + // Fetch and add datasets of the notebook + let datasets = this.notebook.observableNotebookDatasets; + if (datasets) { + datasets.forEach(dataset => { + this.projectService.downloadCSV(dataset.blobFileID).subscribe(data => { + const datasetName = dataset.datasetName; + const datasetData = { + content: data, // The content of the dataset + created: new Date().toISOString(), + format: "text", + last_modified: new Date().toISOString(), + mimetype: 'text/csv', + name: datasetName, + path: datasetName, + size: 0, + type: 'file', + writable: true, + } + + this.jupyterLiteStorageService.addFile(datasetName, datasetData).then( + () => { + }, + (error) => { + console.error('Error adding dataset:', error); + } + ); + }); + }); + } + + // Add the notebook + this.jupyterLiteStorageService.addFile(notebookName, notebookData).then( + () => { + console.log('File added successfully'); + }, + (error) => { + console.error('Error adding file:', error); + } + ); + }); + } + + receiveMessage(event: MessageEvent): void { + if (event.data === 'jupyterlite-load') { + this.isLoading = false; + clearTimeout(this.timeoutId); + console.log('Notebook loaded successfully'); + } + } + + generateObservableNotebook() { + + let script = this._renderer2.createElement('script'); + script.type = `module`; + script.text = this.generateScript; + this._renderer2.appendChild(this.observablehqPanel.nativeElement, script); + } + + get generateScript(): String { + return ` import {Runtime, Inspector} from "https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js"; + var notebookLink = "https://api.` + this.notebook.uri.replace("https://", "") + `.js?v=3"; + import(notebookLink).then((define) =>{ + var notebook = define.default; + (new Runtime).module(notebook, name =>{ + return Inspector.into("#notebook")(); + }); + });` + } + + saveNotebook() { + this.showSaveNotebookModal = true; + } + + onConfirmSaveNotebook() { + if (this.notebook.type === 'notebook' || this.notebook.type === 'new') { + this.commitChangesLoading = true; + this.jupyterLiteStorageService.getFile(`${this.notebook.name}${this.notebook.extension}`).then( + (notebookJson) => { + const notebookBlob = new Blob([JSON.stringify(notebookJson.content)], { type: 'application/json' }); + const file = new File([notebookBlob], `${this.notebook.name}${this.notebook.extension}`, { type: 'application/json' }); + this.notebookFile = { + 'file': file, + 'name': `${this.notebook.name}`, + 'projectID': this.notebook.projectID, + } + this.projectService.uploadNotebookNewVersion(this.notebookFile, this.notebook.directory).subscribe(result => { + this.commitChangesLoading = false; + this.showSaveNotebookModal = false; + }); + }, + (error) => { + console.error('Error getting file:', error); + } + ); + } + } + + onCancelSaveNotebook() { + this.showSaveNotebookModal = false; + } + + closeNotebook() { + this.showSaveWarningModal = true; + } + + onConfirmSave() { + clearTimeout(this.timeoutId); + this.closeModal.emit(); + if (this.notebook.type === 'notebook' || this.notebook.type === 'new') { + this.jupyterLiteStorageService.getFile(`${this.notebook.name}${this.notebook.extension}`).then( + (file) => { + console.log('File:', file); + }, + (error) => { + console.error('Error getting file:', error); + } + ); + this.jupyterLiteStorageService.removeFile(`${this.notebook.name}${this.notebook.extension}`).then( + () => { + console.log('File removed successfully'); + }, + (error) => { + console.error('Error removing file:', error); + } + ); + } + + let datasets = this.notebook.observableNotebookDatasets; + if (datasets) { + datasets.forEach(dataset => { + this.jupyterLiteStorageService.removeFile(dataset.datasetName).then( + () => { + console.log(`Dataset ${dataset.datasetName} removed successfully`); + }, + (error) => { + console.error('Error removing dataset:', error); + } + ); + }); + } + } + + onCancelSave() { + this.showSaveWarningModal = false; + } +} diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/forageIndexDb.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/forageIndexDb.ts new file mode 100644 index 0000000..82ef112 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item-display/forageIndexDb.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@angular/core'; +import * as localforage from 'localforage'; + +@Injectable({ + providedIn: 'root', +}) +export class JupyterLiteStorageService { + private filesStore: LocalForage; + private checkpointsStore: LocalForage; + + constructor() { + this.filesStore = localforage.createInstance({ + name: 'JupyterLite Storage', + storeName: 'files', // Object store name + description: 'Storage for JupyterLite files', + }); + this.checkpointsStore = localforage.createInstance({ + name: 'JupyterLite Storage', + storeName: 'checkpoints', // Object store name + description: 'Storage for JupyterLite checkpoints', + }); + } + + // Add a file to the IndexedDB + addFile(fileName: string, fileData: any): Promise { + return this.filesStore.setItem(fileName, fileData); + } + + // Get a file by its name + getFile(fileName: string): Promise { + return this.filesStore.getItem(fileName); + } + + getCheckpoints(fileName: string): Promise { + return this.checkpointsStore.getItem(fileName); + } + + removeFile(fileName: string): Promise { + return this.filesStore.removeItem(fileName); + } + + // Get all files + getAllFiles(): Promise { + const files: any[] = []; + return this.filesStore.iterate((value, key) => { + files.push({ key, value }); + }).then(() => files); + } +} diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item.component.html b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item.component.html index b06904b..642c6db 100644 --- a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item.component.html +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item.component.html @@ -1 +1,52 @@ -

admin-notebook-item works!

+ +
+ + {{notebook.name+notebook.extension}} + +
+ + + +
+
+ + + + \ No newline at end of file diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item.component.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item.component.ts index 60ed75f..8ccf63c 100644 --- a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item.component.ts +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/admin-notebook-item/admin-notebook-item.component.ts @@ -1,4 +1,8 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, Input, OnInit, TemplateRef, ViewChild } from '@angular/core'; +import { Router } from '@angular/router'; +import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; +import { Notebook } from '../../../../interfaces/notebook'; +import { ProjectService } from '../../../../services/project.service'; @Component({ selector: 'app-admin-notebook-item', @@ -7,9 +11,113 @@ import { Component, OnInit } from '@angular/core'; }) export class AdminNotebookItemComponent implements OnInit { - constructor() { } + constructor(private modalService: BsModalService, private router: Router, private projectService: ProjectService) { } + + @Input() notebook: Notebook; + @ViewChild('displayNotebookModal') displayNotebookModal: TemplateRef; + + @ViewChild('displayRenameNotebookModal') displayRenameNotebookModal: TemplateRef; + @ViewChild('displayDatasetsModal') displayDatasetsModal: TemplateRef; + + displayNotebookModalRef: BsModalRef; + + displayRenameNotebookModalRef: BsModalRef; + + displayDatasetsModalRef: BsModalRef; + + versions: number[] = []; + selectedVersion: number = 0; ngOnInit(): void { + if (this.notebook.type === "new") { + this.loadVersions(); + } + } + + loadVersions() { + this.projectService.getNotebookVersions(this.notebook).subscribe(versions => { + this.versions = versions; + if (this.versions.length > 0) { + this.selectedVersion = this.versions[0]; + } + }); + } + + onVersionChange(version: number) { + this.selectedVersion = version; + } + + showNotebook() { + this.displayNotebookModalRef = this.modalService.show(this.displayNotebookModal); + } + + showRenameNotebookModal() { + this.displayRenameNotebookModalRef = this.modalService.show(this.displayRenameNotebookModal); + } + + closeRenameNotebookModal() { + this.displayRenameNotebookModalRef.hide(); + } + + changeNotebookName(notebookName: string) { + this.notebook.name = notebookName; + } + + navigateToNotebook() { + let datasetParams = {}; + if (this.notebook.type === "observable") { + this.notebook.observableNotebookDatasets.forEach(observableNotebook => { + datasetParams[observableNotebook.datasetName] = observableNotebook.datasetURL; + }) + } + this.router.navigate([this.router.url + "/" + this.notebook.name], { + queryParams: { + isNotebook: true, + notebookId: this.notebook.notebookID, + version: this.selectedVersion, + ...datasetParams + }, queryParamsHandling: 'merge' + }); + } + + deleteNotebook() { + this.projectService.deleteNotebook(this.notebook.notebookID, this.selectedVersion, true).subscribe(res => { + + }) + } + + downloadNotebook() { + this.projectService.downloadNotebook(this.notebook, this.selectedVersion).subscribe(res => { + let url = window.URL.createObjectURL(res); + let a = document.createElement('a'); + document.body.appendChild(a); + a.setAttribute('style', 'display: none'); + a.href = url; + a.download = this.notebook.name + "_v" + this.selectedVersion + this.notebook.extension; + a.click(); + window.URL.revokeObjectURL(url); + a.remove(); + }) + } + + goBack() { + console.log("Go Back"); + } + + openObservableNotebook() { + let datasetParams = {}; + if (this.notebook.type === "observable") { + this.notebook.observableNotebookDatasets.forEach(observableNotebook => { + datasetParams[observableNotebook.datasetName] = observableNotebook.datasetURL; + }) + let url = this.notebook.uri + "?"; + let queryParams = [] + for (const key of Object.keys(datasetParams)) { + queryParams.push(key + "=" + datasetParams[key]); + } + url += queryParams.join('&'); + window.open(url, '_blank'); + } } } diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/notebooks.component.html b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/notebooks.component.html index db96c24..9dbedae 100644 --- a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/notebooks.component.html +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/notebooks.component.html @@ -21,7 +21,7 @@

All Notebooks

{{ i + 1 }} - {{ nb.name+nb.extension }} + {{ nb.route }} {{ @@ -34,3 +34,7 @@

All Notebooks

+ + + diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/notebooks.component.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/notebooks.component.ts index e85aa72..911908a 100644 --- a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/notebooks.component.ts +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/notebooks.component.ts @@ -1,6 +1,9 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { Notebook } from 'src/app/interfaces/notebook'; import { ProjectService } from 'src/app/services/project.service'; +import { Params, Router, ActivatedRoute, NavigationEnd } from '@angular/router'; +import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; + @Component({ selector: 'app-notebooks', @@ -11,11 +14,60 @@ export class NotebooksComponent implements OnInit { notebooks: Notebook[] = []; loading = false; error: string | null = null; + notebookID = null; + version: number = 0; + currentNotebook: Notebook = null; - constructor(private projectService: ProjectService) { } + constructor(private modalService: BsModalService, private projectService: ProjectService, private router: Router, private route: ActivatedRoute) { } + + @ViewChild('displayNotebookModal') displayNotebookModal: TemplateRef; + + displayNotebookModalRef: BsModalRef; ngOnInit(): void { this.loadNotebooks(); + this.router.events.subscribe((ev) => { + if (ev instanceof NavigationEnd) { + this.loadNotebooks(); + } + }); + + this.route.queryParams.subscribe((params: Params) => { + const { + isNotebook, + notebookId, + version + } = params; + this.notebookID = notebookId; + this.version = version; + }) + this.getNotebook(); + } + + getNotebook() { + this.projectService.getNotebook(this.notebookID).subscribe(result => { + this.currentNotebook = result; + this.displayNotebook(this.currentNotebook); + }); + } + + displayNotebook(notebook: Notebook) { + this.currentNotebook = notebook; + // console.log(this.currentNotebook); + this.displayNotebookModalRef = this.modalService.show(this.displayNotebookModal, { + backdrop: 'static', + }); + } + + closeDisplayNotebookModal() { + this.displayNotebookModalRef.hide(); + this.navigateToPreviousComponent(); + } + + navigateToPreviousComponent() { + let previousDirectory = this.router.url.split('/'); + previousDirectory.pop(); + this.router.navigate([previousDirectory.join('/')]) } loadNotebooks() { diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.html b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.html new file mode 100644 index 0000000..2855339 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.html @@ -0,0 +1,9 @@ + \ No newline at end of file diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.scss b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.scss new file mode 100644 index 0000000..312b3f1 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.scss @@ -0,0 +1,78 @@ +.modal-background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + animation: fadeIn 0.3s ease-in-out; + z-index: 4; +} + +.modal-content { + padding: 20px; + border-radius: 10px; + text-align: center; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); + max-width: 400px; + width: 80%; + animation: slideIn 0.3s ease-in-out; +} + +.modal-content p { + font-size: 25px; + margin-bottom: 20px; +} + +button { + background-color: #0a090981; + border: none; + border-radius: 5px; + color: white; + padding: 10px 20px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s ease; + margin: 0 10px; + width: max-content; +} + +.button-css { + display: flex; + justify-content: space-evenly; +} + +button:hover { + background-color: #110a0acf; +} + +button:first-of-type { + background-color: #3769d4; +} + +button:first-of-type:hover { + background-color: #1541af; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes slideIn { + from { + transform: translateY(-20px); + } + + to { + transform: translateY(0); + } +} \ No newline at end of file diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.spec.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.spec.ts new file mode 100644 index 0000000..91e3378 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SaveConfirmationModalComponent } from './save-confirmation-modal.component'; + +describe('SaveConfirmationModalComponent', () => { + let component: SaveConfirmationModalComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SaveConfirmationModalComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SaveConfirmationModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.ts new file mode 100644 index 0000000..c480d5c --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-confirmation-modal/save-confirmation-modal.component.ts @@ -0,0 +1,23 @@ +import { Component, Output, EventEmitter } from '@angular/core'; + +@Component({ + selector: 'app-save-confirmation-modal', + templateUrl: './save-confirmation-modal.component.html', + styleUrls: ['./save-confirmation-modal.component.scss'] +}) +export class SaveConfirmationModalComponent { + @Output() confirmSave = new EventEmitter(); + @Output() cancelSave = new EventEmitter(); + + ngOnInit(): void { + console.log('SaveConfirmationModalComponent loaded'); + } + + onConfirmSave() { + this.confirmSave.emit(); + } + + onCancelSave() { + this.cancelSave.emit(); + } +} diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.html b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.html new file mode 100644 index 0000000..7461d04 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.html @@ -0,0 +1,12 @@ + \ No newline at end of file diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.scss b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.scss new file mode 100644 index 0000000..312b3f1 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.scss @@ -0,0 +1,78 @@ +.modal-background { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; + animation: fadeIn 0.3s ease-in-out; + z-index: 4; +} + +.modal-content { + padding: 20px; + border-radius: 10px; + text-align: center; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); + max-width: 400px; + width: 80%; + animation: slideIn 0.3s ease-in-out; +} + +.modal-content p { + font-size: 25px; + margin-bottom: 20px; +} + +button { + background-color: #0a090981; + border: none; + border-radius: 5px; + color: white; + padding: 10px 20px; + cursor: pointer; + font-size: 16px; + transition: background-color 0.3s ease; + margin: 0 10px; + width: max-content; +} + +.button-css { + display: flex; + justify-content: space-evenly; +} + +button:hover { + background-color: #110a0acf; +} + +button:first-of-type { + background-color: #3769d4; +} + +button:first-of-type:hover { + background-color: #1541af; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes slideIn { + from { + transform: translateY(-20px); + } + + to { + transform: translateY(0); + } +} \ No newline at end of file diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.spec.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.spec.ts new file mode 100644 index 0000000..26848b4 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SaveNotebookModalComponent } from './save-notebook-modal.component'; + +describe('SaveNotebookModalComponent', () => { + let component: SaveNotebookModalComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ SaveNotebookModalComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SaveNotebookModalComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.ts new file mode 100644 index 0000000..b29643e --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/components/notebooks/save-notebook-modal/save-notebook-modal.component.ts @@ -0,0 +1,26 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; + +@Component({ + selector: 'app-save-notebook-modal', + templateUrl: './save-notebook-modal.component.html', + styleUrls: ['./save-notebook-modal.component.scss'] +}) +export class SaveNotebookModalComponent implements OnInit { + + @Input() loading: boolean; + @Output() saveNotebook = new EventEmitter(); + @Output() cancelSave = new EventEmitter(); + + ngOnInit(): void { + console.log('SaveNotebookModalComponent loaded'); + } + + onConfirmSave() { + this.saveNotebook.emit(); + } + + onCancelSave() { + this.cancelSave.emit(); + } + +} diff --git a/src/Analysim.Web/ClientApp/src/app/admin/guards/admin-auth.guard.ts b/src/Analysim.Web/ClientApp/src/app/admin/guards/admin-auth.guard.ts new file mode 100644 index 0000000..81c6d97 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/admin/guards/admin-auth.guard.ts @@ -0,0 +1,14 @@ +// src/app/admin/guards/admin-auth.guard.ts +import { Injectable } from '@angular/core'; +import { CanActivate, Router, UrlTree } from '@angular/router'; + +@Injectable({ providedIn: 'root' }) +export class AdminAuthGuard implements CanActivate { + constructor(private router: Router) {} + + canActivate(): boolean | UrlTree { + const ok = localStorage.getItem('adminLoggedIn') === 'true'; + if (!ok) return this.router.parseUrl('/admin/login'); + return true; + } +} diff --git a/src/Analysim.Web/ClientApp/src/app/projects/projects.module.ts b/src/Analysim.Web/ClientApp/src/app/projects/projects.module.ts index 83d96ac..0632ac9 100644 --- a/src/Analysim.Web/ClientApp/src/app/projects/projects.module.ts +++ b/src/Analysim.Web/ClientApp/src/app/projects/projects.module.ts @@ -118,6 +118,9 @@ import { SaveNotebookModalComponent } from './project-overview/project-overview- ButtonModule, DropdownModule, CheckboxModule + ], + exports: [ + CSVDataBrowserComponent, ] }) export class ProjectsModule { } From 75fdd037041202b76426a7e9e7205c8c8f881a22 Mon Sep 17 00:00:00 2001 From: uday biswas Date: Mon, 28 Jul 2025 00:52:12 +0530 Subject: [PATCH 2/3] implemented admin verification --- .../src/app/admin/admin-routing.module.ts | 8 ++-- .../ClientApp/src/app/admin/admin.module.ts | 5 +-- .../admin-login/admin-login.component.html | 31 ------------- .../admin-login/admin-login.component.scss | 41 ----------------- .../admin-login/admin-login.component.spec.ts | 23 ---------- .../admin-login/admin-login.component.ts | 24 ---------- .../src/app/admin/guards/admin-auth.guard.ts | 14 ------ .../ClientApp/src/app/app-routing.module.ts | 1 + .../ClientApp/src/app/guards/admin.guard.ts | 44 +++++++++++++++++++ .../src/app/navbar/navbar.component.html | 5 +++ .../src/app/navbar/navbar.component.ts | 8 +++- .../src/app/services/account.service.ts | 14 ++++++ .../Controllers/AccountController.cs | 23 ++++++++++ 13 files changed, 97 insertions(+), 144 deletions(-) delete mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.html delete mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.scss delete mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.spec.ts delete mode 100644 src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.ts delete mode 100644 src/Analysim.Web/ClientApp/src/app/admin/guards/admin-auth.guard.ts create mode 100644 src/Analysim.Web/ClientApp/src/app/guards/admin.guard.ts diff --git a/src/Analysim.Web/ClientApp/src/app/admin/admin-routing.module.ts b/src/Analysim.Web/ClientApp/src/app/admin/admin-routing.module.ts index 3f4acd0..27f1a26 100644 --- a/src/Analysim.Web/ClientApp/src/app/admin/admin-routing.module.ts +++ b/src/Analysim.Web/ClientApp/src/app/admin/admin-routing.module.ts @@ -1,20 +1,18 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { AdminComponent } from './admin.component'; -import { AdminLoginComponent } from './components/admin-login/admin-login.component'; -import { AdminAuthGuard } from './guards/admin-auth.guard'; +import { AuthGuardService } from '../guards/auth-guard.service'; +import { AdminGuard } from '../guards/admin.guard'; import { NotebooksComponent } from './components/notebooks/notebooks.component'; import { UsersComponent } from './components/users/users.component'; import { DatasetsComponent } from './components/datasets/datasets.component'; import { ProjectsComponent } from './components/projects/projects.component'; const routes: Routes = [ - { path: 'login', component: AdminLoginComponent }, - { path: '', component: AdminComponent, - canActivate: [AdminAuthGuard], + canActivate: [AuthGuardService, AdminGuard], children: [ { path: 'notebooks/:notebookRoute', component: NotebooksComponent }, { path: 'notebooks', component: NotebooksComponent }, diff --git a/src/Analysim.Web/ClientApp/src/app/admin/admin.module.ts b/src/Analysim.Web/ClientApp/src/app/admin/admin.module.ts index cb56d3c..e246132 100644 --- a/src/Analysim.Web/ClientApp/src/app/admin/admin.module.ts +++ b/src/Analysim.Web/ClientApp/src/app/admin/admin.module.ts @@ -1,6 +1,5 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; -import { RouterModule } from '@angular/router'; import { AdminRoutingModule } from './admin-routing.module'; import { AdminComponent } from './admin.component'; import { NotebooksComponent } from './components/notebooks/notebooks.component'; @@ -13,7 +12,6 @@ import { ProjectsComponent } from './components/projects/projects.component'; import { AdminNotebookItemComponent } from './components/notebooks/admin-notebook-item/admin-notebook-item.component'; import { AdminNotebookItemDisplayComponent } from './components/notebooks/admin-notebook-item/admin-notebook-item-display/admin-notebook-item-display/admin-notebook-item-display.component'; import { ProjectsModule } from '../projects/projects.module'; -import { AdminLoginComponent } from './components/admin-login/admin-login.component'; @NgModule({ declarations: [ @@ -25,8 +23,7 @@ import { AdminLoginComponent } from './components/admin-login/admin-login.compon DatasetsComponent, ProjectsComponent, AdminNotebookItemComponent, - AdminNotebookItemDisplayComponent, - AdminLoginComponent + AdminNotebookItemDisplayComponent ], imports: [ CommonModule, diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.html b/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.html deleted file mode 100644 index 93323d9..0000000 --- a/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.html +++ /dev/null @@ -1,31 +0,0 @@ - diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.scss b/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.scss deleted file mode 100644 index 2cbeb45..0000000 --- a/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.scss +++ /dev/null @@ -1,41 +0,0 @@ -.admin-login { - max-width: 320px; - margin: 80px auto; - padding: 20px; - background: var(--background-color); - border-radius: 8px; - box-shadow: 0 2px 8px rgba(0,0,0,0.1); - - h2 { - text-align: center; - margin-bottom: 1rem; - font-size: 1.5rem; - color: var(--primary-color); - } - - .form-group { - margin-bottom: 1rem; - - label { - display: block; - margin-bottom: 0.5rem; - color: var(--text-color); - } - - .form-control { - width: 100%; - padding: 0.5rem; - border: 1px solid var(--border-color); - border-radius: 4px; - } - } - - .btn { - width: 100%; - } - - .error { - color: #dc3545; - text-align: center; - } -} diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.spec.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.spec.ts deleted file mode 100644 index cf91e2b..0000000 --- a/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { AdminLoginComponent } from './admin-login.component'; - -describe('AdminLoginComponent', () => { - let component: AdminLoginComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - declarations: [ AdminLoginComponent ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(AdminLoginComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.ts b/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.ts deleted file mode 100644 index 39a9cd4..0000000 --- a/src/Analysim.Web/ClientApp/src/app/admin/components/admin-login/admin-login.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Component } from '@angular/core'; -import { Router } from '@angular/router'; - -@Component({ - selector: 'app-admin-login', - templateUrl: './admin-login.component.html', - styleUrls: ['./admin-login.component.scss'] -}) -export class AdminLoginComponent { - username = ''; - password = ''; - error: string | null = null; - - constructor(private router: Router) {} - - login() { - if (this.username === 'ADMIN' && this.password === 'ADMIN') { - localStorage.setItem('adminLoggedIn', 'true'); - this.router.navigateByUrl('/admin'); - } else { - this.error = 'Invalid credentials'; - } - } -} diff --git a/src/Analysim.Web/ClientApp/src/app/admin/guards/admin-auth.guard.ts b/src/Analysim.Web/ClientApp/src/app/admin/guards/admin-auth.guard.ts deleted file mode 100644 index 81c6d97..0000000 --- a/src/Analysim.Web/ClientApp/src/app/admin/guards/admin-auth.guard.ts +++ /dev/null @@ -1,14 +0,0 @@ -// src/app/admin/guards/admin-auth.guard.ts -import { Injectable } from '@angular/core'; -import { CanActivate, Router, UrlTree } from '@angular/router'; - -@Injectable({ providedIn: 'root' }) -export class AdminAuthGuard implements CanActivate { - constructor(private router: Router) {} - - canActivate(): boolean | UrlTree { - const ok = localStorage.getItem('adminLoggedIn') === 'true'; - if (!ok) return this.router.parseUrl('/admin/login'); - return true; - } -} diff --git a/src/Analysim.Web/ClientApp/src/app/app-routing.module.ts b/src/Analysim.Web/ClientApp/src/app/app-routing.module.ts index ffa5fb4..8e7273a 100644 --- a/src/Analysim.Web/ClientApp/src/app/app-routing.module.ts +++ b/src/Analysim.Web/ClientApp/src/app/app-routing.module.ts @@ -35,6 +35,7 @@ const routes: Routes = []; { path: 'profile/:username', component: ProfileComponent }, { path: 'project', loadChildren: () => import('./projects/projects.module').then(m => m.ProjectsModule) }, { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule) }, + { path: '404', component: NotFoundComponent }, { path: '**', component: NotFoundComponent } // todo: Add verify page routing and component ])], diff --git a/src/Analysim.Web/ClientApp/src/app/guards/admin.guard.ts b/src/Analysim.Web/ClientApp/src/app/guards/admin.guard.ts new file mode 100644 index 0000000..b0ee1d9 --- /dev/null +++ b/src/Analysim.Web/ClientApp/src/app/guards/admin.guard.ts @@ -0,0 +1,44 @@ +import { Injectable } from '@angular/core'; +import { + CanActivate, + Router, +} from '@angular/router'; +import { firstValueFrom } from 'rxjs'; +import { take } from 'rxjs/operators'; +import { AccountService } from '../services/account.service'; +import { User } from '../interfaces/user'; + +@Injectable({ providedIn: 'root' }) +export class AdminGuard implements CanActivate { + constructor( + private account: AccountService, + private router: Router + ) {} + + public async canActivate(): Promise { + const isLoggedIn = await firstValueFrom( + this.account.isLoggedIn.pipe(take(1)) + ); + if (!isLoggedIn) { + this.router.navigate(['/login']); + return false; + } + const user$ = await this.account.currentUser; + const user: User = await firstValueFrom( + user$.pipe(take(1)) + ); + if (!user) { + this.router.navigate(['/login']); + return false; + } + const isAdmin = await firstValueFrom( + this.account.getIsAdmin(user.userName).pipe(take(1)) + ); + if (!isAdmin) { + this.router.navigate(['/404']); + return false; + } + + return true; + } +} diff --git a/src/Analysim.Web/ClientApp/src/app/navbar/navbar.component.html b/src/Analysim.Web/ClientApp/src/app/navbar/navbar.component.html index f4f5240..e6e6133 100644 --- a/src/Analysim.Web/ClientApp/src/app/navbar/navbar.component.html +++ b/src/Analysim.Web/ClientApp/src/app/navbar/navbar.component.html @@ -61,6 +61,11 @@