diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index dd9e3e44..c5ebac59 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -50,6 +50,7 @@ import { CopyQemuVmTemplateComponent } from './components/preferences/qemu/copy- import { CopyIosTemplateComponent } from './components/preferences/dynamips/copy-ios-template/copy-ios-template.component'; import { CopyDockerTemplateComponent } from './components/preferences/docker/copy-docker-template/copy-docker-template.component'; import { CopyIouTemplateComponent } from './components/preferences/ios-on-unix/copy-iou-template/copy-iou-template.component'; +import { ListOfSnapshotsComponent } from './components/snapshots/list-of-snapshots/list-of-snapshots.component'; const routes: Routes = [ { @@ -62,6 +63,7 @@ const routes: Routes = [ { path: 'server/:server_id/projects', component: ProjectsComponent }, { path: 'settings', component: SettingsComponent }, { path: 'installed-software', component: InstalledSoftwareComponent }, + { path: 'server/:server_id/project/:project_id/snapshots', component: ListOfSnapshotsComponent }, { path: 'server/:server_id/preferences', component: PreferencesComponent }, // { path: 'server/:server_id/preferences/general', component: GeneralPreferencesComponent }, { path: 'server/:server_id/preferences/builtin', component: BuiltInPreferencesComponent}, @@ -116,7 +118,9 @@ const routes: Routes = [ { path: 'server/:server_id/preferences/iou/addtemplate', component: AddIouTemplateComponent } ] }, - { path: 'server/:server_id/project/:project_id', component: ProjectMapComponent } + { + path: 'server/:server_id/project/:project_id', component: ProjectMapComponent, + } ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7cecaf5f..50690cbe 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -68,7 +68,6 @@ import { RavenState } from './common/error-handlers/raven-state-communicator'; import { ServerDiscoveryComponent } from './components/servers/server-discovery/server-discovery.component'; import { ServerDatabase } from './services/server.database'; import { CreateSnapshotDialogComponent } from './components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component'; -import { SnapshotsComponent } from './components/snapshots/snapshots.component'; import { SnapshotMenuItemComponent } from './components/snapshots/snapshot-menu-item/snapshot-menu-item.component'; import { MATERIAL_IMPORTS } from './material.imports'; import { DrawingService } from './services/drawing.service'; @@ -165,6 +164,8 @@ import { SearchFilter } from './filters/searchFilter.pipe'; import { RecentlyOpenedProjectService } from './services/recentlyOpenedProject.service'; import { ServerManagementService } from './services/server-management.service'; import { DeleteActionComponent } from './components/project-map/context-menu/actions/delete-action/delete-action.component'; +import { ListOfSnapshotsComponent } from './components/snapshots/list-of-snapshots/list-of-snapshots.component'; +import { DateFilter } from './filters/dateFilter.pipe'; if (environment.production) { Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', { @@ -183,7 +184,6 @@ if (environment.production) { AddServerDialogComponent, CreateSnapshotDialogComponent, SnapshotMenuItemComponent, - SnapshotsComponent, ProjectsComponent, AddBlankProjectDialogComponent, ImportProjectDialogComponent, @@ -267,7 +267,9 @@ if (environment.production) { CopyDockerTemplateComponent, EmptyTemplatesListComponent, SymbolsMenuComponent, - SearchFilter + SearchFilter, + DateFilter, + ListOfSnapshotsComponent ], imports: [ BrowserModule, diff --git a/src/app/components/preferences/vpcs/vpcs-templates/vpcs-templates.component.ts b/src/app/components/preferences/vpcs/vpcs-templates/vpcs-templates.component.ts index ad88af64..a2e570d8 100644 --- a/src/app/components/preferences/vpcs/vpcs-templates/vpcs-templates.component.ts +++ b/src/app/components/preferences/vpcs/vpcs-templates/vpcs-templates.component.ts @@ -6,7 +6,6 @@ import { VpcsService } from '../../../../services/vpcs.service'; import { VpcsTemplate } from '../../../../models/templates/vpcs-template'; import { DeleteTemplateComponent } from '../../common/delete-template-component/delete-template.component'; - @Component({ selector: 'app-vpcs-templates', templateUrl: './vpcs-templates.component.html', diff --git a/src/app/components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component.html b/src/app/components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component.html index f81fdfac..ca13ce5e 100644 --- a/src/app/components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component.html +++ b/src/app/components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component.html @@ -1,6 +1,11 @@ -

Create snapshot

+
+

Create snapshot

+ +
- + + +
diff --git a/src/app/components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component.scss b/src/app/components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component.scss index e69de29b..7977448c 100644 --- a/src/app/components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component.scss +++ b/src/app/components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component.scss @@ -0,0 +1,21 @@ +.title-container { + display: flex; + align-items: baseline; + justify-content: space-between; +} + +.name-input { + width: 100%; +} + +.top-button { + outline: none; + box-shadow: none !important; + background: transparent !important; +} + +button:focus { + outline: 0 !important; + border: 0 !important; + box-shadow: none !important; +} diff --git a/src/app/components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component.ts b/src/app/components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component.ts index ed415f68..739c543a 100644 --- a/src/app/components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component.ts +++ b/src/app/components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component.ts @@ -1,6 +1,9 @@ import { Component, Inject } from '@angular/core'; import { Snapshot } from '../../../models/snapshot'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; +import { Server } from '../../../models/server'; +import { Project } from '../../../models/project'; + @Component({ selector: 'app-create-snapshot-dialog', @@ -8,12 +11,17 @@ import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material'; styleUrls: ['./create-snapshot-dialog.component.scss'] }) export class CreateSnapshotDialogComponent { + server: Server; + project: Project; snapshot: Snapshot = new Snapshot(); constructor( public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any - ) {} + ) { + this.server = data['server']; + this.project = data['project']; + } onAddClick(): void { this.dialogRef.close(this.snapshot); diff --git a/src/app/components/snapshots/snapshots.component.scss b/src/app/components/snapshots/list-of-snapshots/list-of-snaphshots.component.spec.ts similarity index 100% rename from src/app/components/snapshots/snapshots.component.scss rename to src/app/components/snapshots/list-of-snapshots/list-of-snaphshots.component.spec.ts diff --git a/src/app/components/snapshots/list-of-snapshots/list-of-snapshots.component.html b/src/app/components/snapshots/list-of-snapshots/list-of-snapshots.component.html new file mode 100644 index 00000000..8f9adb59 --- /dev/null +++ b/src/app/components/snapshots/list-of-snapshots/list-of-snapshots.component.html @@ -0,0 +1,38 @@ +
+
+
+

Snapshots

+
+
+
+
+ + + Name + {{row.name}} + + + + Date + {{row.created_at | datefilter}} + + + + Actions + + + + + + + + + + +
+
+
diff --git a/src/app/components/snapshots/list-of-snapshots/list-of-snapshots.component.scss b/src/app/components/snapshots/list-of-snapshots/list-of-snapshots.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/snapshots/list-of-snapshots/list-of-snapshots.component.ts b/src/app/components/snapshots/list-of-snapshots/list-of-snapshots.component.ts new file mode 100644 index 00000000..8b25083c --- /dev/null +++ b/src/app/components/snapshots/list-of-snapshots/list-of-snapshots.component.ts @@ -0,0 +1,94 @@ +import { Component, OnInit } from '@angular/core'; +import { SnapshotService } from '../../../services/snapshot.service'; +import { ServerService } from '../../../services/server.service'; +import { ActivatedRoute } from '@angular/router'; +import { Server } from '../../..//models/server'; +import { Snapshot } from '../../../models/snapshot'; +import { Sort } from '@angular/material'; +import { ProgressDialogService } from '../../../common/progress-dialog/progress-dialog.service'; +import { ToasterService } from '../../../services/toaster.service'; +import { Project } from '../../../models/project'; +import { ProgressDialogComponent } from '../../../common/progress-dialog/progress-dialog.component'; + +@Component({ + selector: 'app-list-of-snapshots', + templateUrl: './list-of-snapshots.component.html', + styleUrls: ['./list-of-snapshots.component.scss'] +}) +export class ListOfSnapshotsComponent implements OnInit { + server: Server; + projectId: string; + snapshots: Snapshot[]; + displayedColumns = ['name', 'creationDate', 'actions']; + + constructor( + private snapshotService: SnapshotService, + private serverService: ServerService, + private route: ActivatedRoute, + private progressDialogService: ProgressDialogService, + private toaster: ToasterService + ) {} + + ngOnInit() { + let serverId = this.route.snapshot.paramMap.get("server_id"); + this.projectId = this.route.snapshot.paramMap.get("project_id"); + this.serverService.get(parseInt(serverId, 10)).then((server: Server) => { + this.server = server; + this.getSnapshots(); + }); + } + + getSnapshots() { + this.snapshotService.list(this.server, this.projectId).subscribe((snapshots: Snapshot[]) => { + this.snapshots = snapshots; + }); + } + + restoreSnapshot(snapshot: Snapshot) { + const restoring = this.snapshotService.restore(this.server, this.projectId, snapshot.snapshot_id.toString()); + const progress = this.progressDialogService.open(); + const subscription = restoring.subscribe( + (project: Project) => { + this.toaster.success(`Snapshot ${snapshot.name} has been restored.`); + progress.close(); + } + ); + + progress.afterClosed().subscribe(result => { + if (result === ProgressDialogComponent.CANCELLED) { + subscription.unsubscribe(); + } + }); + } + + deleteSnapshot(snapshot: Snapshot) { + this.snapshotService.delete(this.server, this.projectId, snapshot.snapshot_id.toString()).subscribe(() => { + this.getSnapshots(); + this.toaster.success(`Snapshot ${snapshot.name} has been deleted.`); + }); + } + + sortData(sort: Sort) { + if (!sort.active || sort.direction === '') return; + + let snapshots = this.snapshots.slice(); + this.snapshots = snapshots.sort((a, b) => { + const isAsc = sort.direction === 'asc'; + if (sort.active === 'name') { + return compareNames(a.name, b.name, isAsc); + } else if (sort.active === 'creationDate') { + return compareDates(+a.created_at, +b.created_at, !isAsc); + } else return 0; + }); + } +} + +function compareDates(a: number, b: number, isAsc: boolean) { + return (a < b ? -1 : 1) * (isAsc ? 1 : -1); +} + +function compareNames(a: string, b: string, isAsc: boolean) { + a = a.toLowerCase(); + b = b.toLowerCase(); + return (a < b ? -1 : 1) * (isAsc ? 1 : -1); +} diff --git a/src/app/components/snapshots/snapshot-menu-item/snapshot-menu-item.component.ts b/src/app/components/snapshots/snapshot-menu-item/snapshot-menu-item.component.ts index d2dc6a93..066a31e3 100644 --- a/src/app/components/snapshots/snapshot-menu-item/snapshot-menu-item.component.ts +++ b/src/app/components/snapshots/snapshot-menu-item/snapshot-menu-item.component.ts @@ -30,7 +30,11 @@ export class SnapshotMenuItemComponent implements OnInit { public createSnapshotModal() { const dialogRef = this.dialog.open(CreateSnapshotDialogComponent, { - width: '250px' + width: '450px', + data: { + server: this.server, + project: this.project + } }); dialogRef.afterClosed().subscribe(snapshot => { diff --git a/src/app/components/snapshots/snapshots.component.html b/src/app/components/snapshots/snapshots.component.html deleted file mode 100644 index fc5228ca..00000000 --- a/src/app/components/snapshots/snapshots.component.html +++ /dev/null @@ -1 +0,0 @@ -

snapshots works!

diff --git a/src/app/components/snapshots/snapshots.component.spec.ts b/src/app/components/snapshots/snapshots.component.spec.ts deleted file mode 100644 index 1e49c3d8..00000000 --- a/src/app/components/snapshots/snapshots.component.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; - -import { SnapshotsComponent } from './snapshots.component'; - -describe('SnapshotsComponent', () => { - let component: SnapshotsComponent; - let fixture: ComponentFixture; - - beforeEach(async(() => { - TestBed.configureTestingModule({ - declarations: [SnapshotsComponent] - }).compileComponents(); - })); - - beforeEach(() => { - fixture = TestBed.createComponent(SnapshotsComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/components/snapshots/snapshots.component.ts b/src/app/components/snapshots/snapshots.component.ts deleted file mode 100644 index bf0984e9..00000000 --- a/src/app/components/snapshots/snapshots.component.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Component, OnInit } from '@angular/core'; - -@Component({ - selector: 'app-snapshots', - templateUrl: './snapshots.component.html', - styleUrls: ['./snapshots.component.scss'] -}) -export class SnapshotsComponent implements OnInit { - constructor() {} - - ngOnInit() {} -} diff --git a/src/app/filters/dateFilter.pipe.spec.ts b/src/app/filters/dateFilter.pipe.spec.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/filters/dateFilter.pipe.ts b/src/app/filters/dateFilter.pipe.ts new file mode 100644 index 00000000..930a8683 --- /dev/null +++ b/src/app/filters/dateFilter.pipe.ts @@ -0,0 +1,21 @@ +import { Pipe, PipeTransform } from '@angular/core'; + + +@Pipe({ + name: 'datefilter' +}) +export class DateFilter implements PipeTransform { + transform(timestamp: string): string { + let date = new Date(+timestamp*1000); + + let hours = date.getHours(); + let minutes = "0" + date.getMinutes(); + let seconds = "0" + date.getSeconds(); + + let year = date.getFullYear(); + let month = date.getMonth() + 1; + let day = date.getDate(); + + return hours + ':' + minutes.substr(-2) + ':' + seconds.substr(-2) + ' ' + day + '/' + month + '/' + year; + } +} diff --git a/src/app/services/snapshot.service.ts b/src/app/services/snapshot.service.ts index 1613606b..ea36c657 100644 --- a/src/app/services/snapshot.service.ts +++ b/src/app/services/snapshot.service.ts @@ -1,6 +1,5 @@ import { Injectable } from '@angular/core'; import { Server } from '../models/server'; -import { Observable } from 'rxjs'; import { HttpServer } from './http-server.service'; import { Snapshot } from '../models/snapshot'; @@ -12,7 +11,15 @@ export class SnapshotService { return this.httpServer.post(server, `/projects/${project_id}/snapshots`, snapshot); } + delete(server: Server, project_id: string, snapshot_id: string) { + return this.httpServer.delete(server, `/projects/${project_id}/snapshots/${snapshot_id}`); + } + list(server: Server, project_id: string) { return this.httpServer.get(server, `/projects/${project_id}/snapshots`); } + + restore(server: Server, project_id: string, snapshot_id: string) { + return this.httpServer.post(server, `/projects/${project_id}/snapshots/${snapshot_id}/restore`, {}); + } }