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 c5938619..223999cc 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,9 @@ 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'; +import { NameFilter } from './filters/nameFilter.pipe'; import { CustomAdaptersComponent } from './components/preferences/common/custom-adapters/custom-adapters.component'; if (environment.production) { @@ -184,7 +186,6 @@ if (environment.production) { AddServerDialogComponent, CreateSnapshotDialogComponent, SnapshotMenuItemComponent, - SnapshotsComponent, ProjectsComponent, AddBlankProjectDialogComponent, ImportProjectDialogComponent, @@ -269,6 +270,9 @@ if (environment.production) { EmptyTemplatesListComponent, SymbolsMenuComponent, SearchFilter, + DateFilter, + NameFilter, + ListOfSnapshotsComponent, CustomAdaptersComponent ], imports: [ diff --git a/src/app/common/progress-dialog/progress-dialog.service.ts b/src/app/common/progress-dialog/progress-dialog.service.ts index 07b757dc..ab7e4462 100644 --- a/src/app/common/progress-dialog/progress-dialog.service.ts +++ b/src/app/common/progress-dialog/progress-dialog.service.ts @@ -8,7 +8,8 @@ export class ProgressDialogService { public open() { const ref = this.dialog.open(ProgressDialogComponent, { - width: '250px' + width: '250px', + autoFocus: false }); return ref; } diff --git a/src/app/components/preferences/common/delete-template-component/delete-template.component.ts b/src/app/components/preferences/common/delete-template-component/delete-template.component.ts index d4495bea..b300866a 100644 --- a/src/app/components/preferences/common/delete-template-component/delete-template.component.ts +++ b/src/app/components/preferences/common/delete-template-component/delete-template.component.ts @@ -26,7 +26,8 @@ export class DeleteTemplateComponent { height: '250px', data: { templateName: templateName - } + }, + autoFocus: false }); dialogRef.afterClosed().subscribe((answer: boolean) => { diff --git a/src/app/components/preferences/docker/add-docker-template/add-docker-template.component.spec.ts b/src/app/components/preferences/docker/add-docker-template/add-docker-template.component.spec.ts index 2f306680..0617109a 100644 --- a/src/app/components/preferences/docker/add-docker-template/add-docker-template.component.spec.ts +++ b/src/app/components/preferences/docker/add-docker-template/add-docker-template.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, async, TestBed } from '@angular/core/testing'; -import { MatInputModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatSelectModule, MatFormFieldModule, MatAutocompleteModule, MatTableModule } from '@angular/material'; +import { MatInputModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatSelectModule, MatFormFieldModule, MatAutocompleteModule, MatTableModule, MatStepperModule } from '@angular/material'; import { CommonModule } from '@angular/common'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; @@ -36,7 +36,7 @@ describe('AddDockerTemplateComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [ FormsModule, MatTableModule, MatAutocompleteModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, MatSelectModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])], + imports: [MatStepperModule, FormsModule, MatTableModule, MatAutocompleteModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, MatSelectModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])], providers: [ { provide: ActivatedRoute, useValue: activatedRoute diff --git a/src/app/components/preferences/dynamips/add-ios-template/add-ios-template.component.spec.ts b/src/app/components/preferences/dynamips/add-ios-template/add-ios-template.component.spec.ts index 942dcb36..876f29c3 100644 --- a/src/app/components/preferences/dynamips/add-ios-template/add-ios-template.component.spec.ts +++ b/src/app/components/preferences/dynamips/add-ios-template/add-ios-template.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, async, TestBed } from '@angular/core/testing'; -import { MatInputModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatSelectModule, MatFormFieldModule, MatAutocompleteModule, MatTableModule } from '@angular/material'; +import { MatInputModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatSelectModule, MatFormFieldModule, MatAutocompleteModule, MatTableModule, MatStepperModule } from '@angular/material'; import { CommonModule } from '@angular/common'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; @@ -36,7 +36,7 @@ describe('AddIosTemplateComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [ FormsModule, MatTableModule, MatAutocompleteModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, MatSelectModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])], + imports: [MatStepperModule, FormsModule, MatTableModule, MatAutocompleteModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, MatSelectModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])], providers: [ { provide: ActivatedRoute, useValue: activatedRoute diff --git a/src/app/components/preferences/ios-on-unix/add-iou-template/add-iou-template.component.spec.ts b/src/app/components/preferences/ios-on-unix/add-iou-template/add-iou-template.component.spec.ts index a64f24f2..3db5d846 100644 --- a/src/app/components/preferences/ios-on-unix/add-iou-template/add-iou-template.component.spec.ts +++ b/src/app/components/preferences/ios-on-unix/add-iou-template/add-iou-template.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, async, TestBed } from '@angular/core/testing'; -import { MatInputModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatSelectModule, MatFormFieldModule, MatAutocompleteModule, MatTableModule } from '@angular/material'; +import { MatInputModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatSelectModule, MatFormFieldModule, MatAutocompleteModule, MatTableModule, MatStepperModule } from '@angular/material'; import { CommonModule } from '@angular/common'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; @@ -36,7 +36,7 @@ describe('AddIouTemplateComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [ FormsModule, MatTableModule, MatAutocompleteModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, MatSelectModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])], + imports: [MatStepperModule, FormsModule, MatTableModule, MatAutocompleteModule, MatFormFieldModule, MatInputModule, ReactiveFormsModule, MatSelectModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])], providers: [ { provide: ActivatedRoute, useValue: activatedRoute }, { provide: ServerService, useValue: mockedServerService }, diff --git a/src/app/components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component.spec.ts b/src/app/components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component.spec.ts index 3afd6b0b..48012eee 100644 --- a/src/app/components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component.spec.ts +++ b/src/app/components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, async, TestBed } from '@angular/core/testing'; -import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatSelectModule, MatAutocompleteModule, MatFormFieldModule, MatInputModule } from '@angular/material'; +import { MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatSelectModule, MatAutocompleteModule, MatFormFieldModule, MatInputModule, MatStepperModule } from '@angular/material'; import { CommonModule } from '@angular/common'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { RouterTestingModule } from '@angular/router/testing'; @@ -47,7 +47,7 @@ describe('AddQemuVmTemplateComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - imports: [FormsModule, ReactiveFormsModule, MatSelectModule, MatAutocompleteModule, MatIconModule, MatFormFieldModule, MatInputModule, + imports: [MatStepperModule, FormsModule, ReactiveFormsModule, MatSelectModule, MatAutocompleteModule, MatIconModule, MatFormFieldModule, MatInputModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])], providers: [ { 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/project-map/context-menu/actions/edit-style-action/edit-style-action.component.ts b/src/app/components/project-map/context-menu/actions/edit-style-action/edit-style-action.component.ts index 5234c05b..ea7e8cdf 100644 --- a/src/app/components/project-map/context-menu/actions/edit-style-action/edit-style-action.component.ts +++ b/src/app/components/project-map/context-menu/actions/edit-style-action/edit-style-action.component.ts @@ -20,7 +20,8 @@ export class EditStyleActionComponent implements OnInit { editStyle() { const dialogRef = this.dialog.open(StyleEditorDialogComponent, { - width: '300px' + width: '300px', + autoFocus: false }); let instance = dialogRef.componentInstance; instance.server = this.server; diff --git a/src/app/components/project-map/context-menu/actions/edit-text-action/edit-text-action.component.ts b/src/app/components/project-map/context-menu/actions/edit-text-action/edit-text-action.component.ts index cc2f0720..2486a0c7 100644 --- a/src/app/components/project-map/context-menu/actions/edit-text-action/edit-text-action.component.ts +++ b/src/app/components/project-map/context-menu/actions/edit-text-action/edit-text-action.component.ts @@ -20,7 +20,8 @@ export class EditTextActionComponent implements OnInit { editText() { const dialogRef = this.dialog.open(TextEditorDialogComponent, { - width: '300px' + width: '300px', + autoFocus: false }); let instance = dialogRef.componentInstance; instance.server = this.server; diff --git a/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.ts b/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.ts index 6536836c..523860fe 100644 --- a/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.ts +++ b/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.ts @@ -83,7 +83,8 @@ export class AddBlankProjectDialogComponent implements OnInit { height: '150px', data: { existingProject: existingProject - } + }, + autoFocus: false }); dialogRef.afterClosed().subscribe((answer: boolean) => { diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts index 59ad09ee..07a76027 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts @@ -106,7 +106,8 @@ export class ImportProjectDialogComponent implements OnInit { height: '150px', data: { existingProject: existingProject - } + }, + autoFocus: false }); dialogRef.afterClosed().subscribe((answer: boolean) => { diff --git a/src/app/components/projects/projects.component.ts b/src/app/components/projects/projects.component.ts index f6ae4fbb..02741faf 100644 --- a/src/app/components/projects/projects.component.ts +++ b/src/app/components/projects/projects.component.ts @@ -109,7 +109,8 @@ export class ProjectsComponent implements OnInit { addBlankProject() { const dialogRef = this.dialog.open(AddBlankProjectDialogComponent, { - width: '400px' + width: '400px', + autoFocus: false }); let instance = dialogRef.componentInstance; instance.server = this.server; @@ -117,7 +118,8 @@ export class ProjectsComponent implements OnInit { importProject() { const dialogRef = this.dialog.open(ImportProjectDialogComponent, { - width: '400px' + width: '400px', + autoFocus: false }); let instance = dialogRef.componentInstance; instance.server = this.server; diff --git a/src/app/components/servers/servers.component.ts b/src/app/components/servers/servers.component.ts index 57cdba3c..d487e2d4 100644 --- a/src/app/components/servers/servers.component.ts +++ b/src/app/components/servers/servers.component.ts @@ -69,7 +69,8 @@ export class ServersComponent implements OnInit, OnDestroy { createModal() { const dialogRef = this.dialog.open(AddServerDialogComponent, { - width: '350px' + width: '350px', + autoFocus: false }); dialogRef.afterClosed().subscribe(server => { 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/list-of-snapshots/list-of-snaphshots.component.spec.ts b/src/app/components/snapshots/list-of-snapshots/list-of-snaphshots.component.spec.ts new file mode 100644 index 00000000..b68ef220 --- /dev/null +++ b/src/app/components/snapshots/list-of-snapshots/list-of-snaphshots.component.spec.ts @@ -0,0 +1,143 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ListOfSnapshotsComponent } from './list-of-snapshots.component'; +import { MatTableModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, MatDialogModule, Sort } from '@angular/material'; +import { CommonModule } from '@angular/common'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { RouterTestingModule } from '@angular/router/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { SnapshotService } from '../../../services/snapshot.service'; +import { DateFilter } from '../../../filters/dateFilter.pipe'; +import { of } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; +import { ServerService } from '../../../services/server.service'; +import { MockedServerService } from '../../../services/server.service.spec'; +import { ProgressDialogService } from '../../../common/progress-dialog/progress-dialog.service'; +import { ToasterService } from '../../../services/toaster.service'; +import { Server } from '../../../models/server'; +import { Snapshot } from '../../../models/snapshot'; +import { MockedToasterService } from '../../../services/toaster.service.spec'; +import { NameFilter } from '../../../filters/nameFilter.pipe'; + +export class MockedActivatedRoute { + get() { + return { + params: of({ id: 3 }), + snapshot: { + parent: { + params: { + id: 1 + } + }, + paramMap: { + get(name: string): string { + return '1'; + } + } + }, + }; + } +} + +export class MockedSnapshotService { + public list(server: Server, project_id: string) { + return of([]); + } + + public delete(server: Server, project_id: string, snapshot_id: string) { + return of({}); + } + + public restore(server: Server, project_id: string, snapshot_id: string) { + return of({}); + } +} + +describe('ListOfSnapshotsComponent', () => { + let component: ListOfSnapshotsComponent; + let fixture: ComponentFixture; + let activatedRoute = new MockedActivatedRoute().get(); + let mockedServerService = new MockedServerService(); + let mockedSnapshotService = new MockedSnapshotService(); + let mockedToasterService = new MockedToasterService(); + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [MatDialogModule, MatTableModule, MatIconModule, MatToolbarModule, MatMenuModule, MatCheckboxModule, CommonModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])], + providers: [ + { provide: SnapshotService, useValue: mockedSnapshotService }, + { provide: ActivatedRoute, useValue: activatedRoute }, + { provide: ServerService, useValue: mockedServerService }, + { provide: ProgressDialogService, useClass: ProgressDialogService }, + { provide: ToasterService, useValue: mockedToasterService } + ], + declarations: [ + ListOfSnapshotsComponent, + DateFilter, + NameFilter + ], + schemas: [NO_ERRORS_SCHEMA] + }).compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ListOfSnapshotsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should call snapshot service to get items', () => { + spyOn(mockedSnapshotService, 'list').and.returnValues(of([])); + + component.getSnapshots(); + + expect(mockedSnapshotService.list).toHaveBeenCalled(); + }); + + it('should call snapshot service to delete snapshot', () => { + let snapshot: Snapshot = { + snapshot_id: 1, + name: 'snapshot1', + created_at: '111111', + project_id: 1 + }; + spyOn(mockedSnapshotService, 'delete').and.returnValues(of([])); + + component.deleteSnapshot(snapshot); + + expect(mockedSnapshotService.delete).toHaveBeenCalled(); + }); + + it('should sort snapshots in correct order', () => { + component.snapshots = [ + { + snapshot_id: 2, + name: 'second snapshot', + created_at: '222222', + project_id: 1 + }, + { + snapshot_id: 1, + name: 'first snapshot', + created_at: '111111', + project_id: 1 + } + ]; + let sort: Sort = { + active: 'name', + direction: 'asc' + }; + + component.sortData(sort); + + expect(component.snapshots[0].snapshot_id).toBe(1); + + sort.direction = 'desc'; + component.sortData(sort); + + expect(component.snapshots[0].snapshot_id).toBe(2); + }); +}); 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..0ff03824 --- /dev/null +++ b/src/app/components/snapshots/list-of-snapshots/list-of-snapshots.component.html @@ -0,0 +1,46 @@ +
+
+
+

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..84536106 --- /dev/null +++ b/src/app/components/snapshots/list-of-snapshots/list-of-snapshots.component.scss @@ -0,0 +1,7 @@ +.filter-field { + width: 100%; +} + +.mat-table { + margin: -16px!important; +} 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..9ca04c49 --- /dev/null +++ b/src/app/components/snapshots/list-of-snapshots/list-of-snapshots.component.ts @@ -0,0 +1,95 @@ +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']; + searchText: string; + + 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..78a806b8 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,12 @@ export class SnapshotMenuItemComponent implements OnInit { public createSnapshotModal() { const dialogRef = this.dialog.open(CreateSnapshotDialogComponent, { - width: '250px' + width: '450px', + data: { + server: this.server, + project: this.project + }, + autoFocus: false }); 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/components/template/template.component.ts b/src/app/components/template/template.component.ts index 5a3e4986..9b6675ca 100644 --- a/src/app/components/template/template.component.ts +++ b/src/app/components/template/template.component.ts @@ -24,7 +24,8 @@ export class TemplateComponent implements OnInit { height: '560px', data: { server: this.server - } + }, + autoFocus: false }); dialogRef.afterClosed().subscribe((template: Template) => { diff --git a/src/app/components/snapshots/snapshots.component.scss b/src/app/filters/dateFilter.pipe.spec.ts similarity index 100% rename from src/app/components/snapshots/snapshots.component.scss rename to src/app/filters/dateFilter.pipe.spec.ts 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/filters/nameFilter.pipe.spec.ts b/src/app/filters/nameFilter.pipe.spec.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/app/filters/nameFilter.pipe.ts b/src/app/filters/nameFilter.pipe.ts new file mode 100644 index 00000000..80514b2a --- /dev/null +++ b/src/app/filters/nameFilter.pipe.ts @@ -0,0 +1,17 @@ +import { Pipe, PipeTransform } from '@angular/core'; + + +@Pipe({ + name: 'namefilter' +}) +export class NameFilter implements PipeTransform { + transform(items: any[], searchText: string): any[] { + if(!items) return []; + if(!searchText) return items; + + searchText = searchText.toLowerCase(); + return items.filter( item => { + return item.name.toLowerCase().includes(searchText); + }); + } +} 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`, {}); + } }