diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 6ea67ee9..30246a4d 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -1,78 +1,62 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes} from '@angular/router'; -import {BundledServerFinderComponent} from './components/bundled-server-finder/bundled-server-finder.component'; -import {DirectLinkComponent} from './components/direct-link/direct-link.component'; -import {HelpComponent} from './components/help/help.component'; -import {InstalledSoftwareComponent} from './components/installed-software/installed-software.component'; -import {LoginComponent} from './components/login/login.component'; -import {PageNotFoundComponent} from './components/page-not-found/page-not-found.component'; -import {BuiltInPreferencesComponent} from './components/preferences/built-in/built-in-preferences.component'; -import {CloudNodesAddTemplateComponent} from './components/preferences/built-in/cloud-nodes/cloud-nodes-add-template/cloud-nodes-add-template.component'; -import {CloudNodesTemplateDetailsComponent} from './components/preferences/built-in/cloud-nodes/cloud-nodes-template-details/cloud-nodes-template-details.component'; -import {CloudNodesTemplatesComponent} from './components/preferences/built-in/cloud-nodes/cloud-nodes-templates/cloud-nodes-templates.component'; -import {EthernetHubsAddTemplateComponent} from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-add-template/ethernet-hubs-add-template.component'; -import {EthernetHubsTemplateDetailsComponent} from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-template-details/ethernet-hubs-template-details.component'; -import {EthernetHubsTemplatesComponent} from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-templates/ethernet-hubs-templates.component'; -import {EthernetSwitchesAddTemplateComponent} from './components/preferences/built-in/ethernet-switches/ethernet-switches-add-template/ethernet-switches-add-template.component'; -import {EthernetSwitchesTemplateDetailsComponent} from './components/preferences/built-in/ethernet-switches/ethernet-switches-template-details/ethernet-switches-template-details.component'; -import {EthernetSwitchesTemplatesComponent} from './components/preferences/built-in/ethernet-switches/ethernet-switches-templates/ethernet-switches-templates.component'; -import {AddDockerTemplateComponent} from './components/preferences/docker/add-docker-template/add-docker-template.component'; -import {CopyDockerTemplateComponent} from './components/preferences/docker/copy-docker-template/copy-docker-template.component'; -import {DockerTemplateDetailsComponent} from './components/preferences/docker/docker-template-details/docker-template-details.component'; -import {DockerTemplatesComponent} from './components/preferences/docker/docker-templates/docker-templates.component'; -import {AddIosTemplateComponent} from './components/preferences/dynamips/add-ios-template/add-ios-template.component'; -import {CopyIosTemplateComponent} from './components/preferences/dynamips/copy-ios-template/copy-ios-template.component'; -import {IosTemplateDetailsComponent} from './components/preferences/dynamips/ios-template-details/ios-template-details.component'; -import {IosTemplatesComponent} from './components/preferences/dynamips/ios-templates/ios-templates.component'; -import {AddIouTemplateComponent} from './components/preferences/ios-on-unix/add-iou-template/add-iou-template.component'; -import {CopyIouTemplateComponent} from './components/preferences/ios-on-unix/copy-iou-template/copy-iou-template.component'; -import {IouTemplateDetailsComponent} from './components/preferences/ios-on-unix/iou-template-details/iou-template-details.component'; -import {IouTemplatesComponent} from './components/preferences/ios-on-unix/iou-templates/iou-templates.component'; -import {PreferencesComponent} from './components/preferences/preferences.component'; -import {AddQemuVmTemplateComponent} from './components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component'; -import {CopyQemuVmTemplateComponent} from './components/preferences/qemu/copy-qemu-vm-template/copy-qemu-vm-template.component'; -import {QemuVmTemplateDetailsComponent} from './components/preferences/qemu/qemu-vm-template-details/qemu-vm-template-details.component'; -import {QemuVmTemplatesComponent} from './components/preferences/qemu/qemu-vm-templates/qemu-vm-templates.component'; -import {AddVirtualBoxTemplateComponent} from './components/preferences/virtual-box/add-virtual-box-template/add-virtual-box-template.component'; -import {VirtualBoxTemplateDetailsComponent} from './components/preferences/virtual-box/virtual-box-template-details/virtual-box-template-details.component'; -import {VirtualBoxTemplatesComponent} from './components/preferences/virtual-box/virtual-box-templates/virtual-box-templates.component'; -import {AddVmwareTemplateComponent} from './components/preferences/vmware/add-vmware-template/add-vmware-template.component'; -import {VmwareTemplateDetailsComponent} from './components/preferences/vmware/vmware-template-details/vmware-template-details.component'; -import {VmwareTemplatesComponent} from './components/preferences/vmware/vmware-templates/vmware-templates.component'; -import {AddVpcsTemplateComponent} from './components/preferences/vpcs/add-vpcs-template/add-vpcs-template.component'; -import {VpcsTemplateDetailsComponent} from './components/preferences/vpcs/vpcs-template-details/vpcs-template-details.component'; -import {VpcsTemplatesComponent} from './components/preferences/vpcs/vpcs-templates/vpcs-templates.component'; -import {ProjectMapComponent} from './components/project-map/project-map.component'; -import {ProjectsComponent} from './components/projects/projects.component'; -import {ServersComponent} from './components/servers/servers.component'; -import {ConsoleComponent} from './components/settings/console/console.component'; -import {SettingsComponent} from './components/settings/settings.component'; -import {ListOfSnapshotsComponent} from './components/snapshots/list-of-snapshots/list-of-snapshots.component'; -import {SystemStatusComponent} from './components/system-status/system-status.component'; -import {WebConsoleFullWindowComponent} from './components/web-console-full-window/web-console-full-window.component'; -import {ConsoleGuard} from './guards/console-guard'; -import {LoginGuard} from './guards/login-guard'; -import {DefaultLayoutComponent} from './layouts/default-layout/default-layout.component'; -import {ServerResolve} from './resolvers/server-resolve'; -import {UserManagementComponent} from './components/user-management/user-management.component'; -import {LoggedUserComponent} from './components/users/logged-user/logged-user.component'; -import {GroupManagementComponent} from "./components/group-management/group-management.component"; -import {GroupDetailsComponent} from "@components/group-details/group-details.component"; -import {UserDetailComponent} from "@components/user-management/user-detail/user-detail.component"; -import {GroupMembersResolver} from "@resolvers/group-members.resolver"; -import {ManagementComponent} from "@components/management/management.component"; -import {RoleManagementComponent} from "@components/role-management/role-management.component"; -import {RoleDetailComponent} from "@components/role-management/role-detail/role-detail.component"; -import {RoleDetailResolver} from "@resolvers/role-detail.resolver"; -import {PermissionResolver} from "@resolvers/permission.resolver"; -import {GroupResolver} from "@resolvers/group.resolver"; -import {GroupRoleResolver} from "@resolvers/group-role.resolver"; -import {PermissionsManagementComponent} from "@components/permissions-management/permissions-management.component"; -import {UserDetailResolver} from "@resolvers/user-detail.resolver"; -import {UserGroupsResolver} from "@resolvers/user-groups.resolver"; -import {UserPermissionsResolver} from "@resolvers/user-permissions.resolver"; -import {RolePermissionsComponent} from "@components/role-management/role-detail/role-permissions/role-permissions.component"; -import {UserPermissionsComponent} from "@components/user-management/user-detail/user-permissions/user-permissions.component"; +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { BundledServerFinderComponent } from './components/bundled-server-finder/bundled-server-finder.component'; +import { DirectLinkComponent } from './components/direct-link/direct-link.component'; +import { HelpComponent } from './components/help/help.component'; +import { InstalledSoftwareComponent } from './components/installed-software/installed-software.component'; +import { LoginComponent } from './components/login/login.component'; +import { PageNotFoundComponent } from './components/page-not-found/page-not-found.component'; +import { BuiltInPreferencesComponent } from './components/preferences/built-in/built-in-preferences.component'; +import { CloudNodesAddTemplateComponent } from './components/preferences/built-in/cloud-nodes/cloud-nodes-add-template/cloud-nodes-add-template.component'; +import { CloudNodesTemplateDetailsComponent } from './components/preferences/built-in/cloud-nodes/cloud-nodes-template-details/cloud-nodes-template-details.component'; +import { CloudNodesTemplatesComponent } from './components/preferences/built-in/cloud-nodes/cloud-nodes-templates/cloud-nodes-templates.component'; +import { EthernetHubsAddTemplateComponent } from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-add-template/ethernet-hubs-add-template.component'; +import { EthernetHubsTemplateDetailsComponent } from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-template-details/ethernet-hubs-template-details.component'; +import { EthernetHubsTemplatesComponent } from './components/preferences/built-in/ethernet-hubs/ethernet-hubs-templates/ethernet-hubs-templates.component'; +import { EthernetSwitchesAddTemplateComponent } from './components/preferences/built-in/ethernet-switches/ethernet-switches-add-template/ethernet-switches-add-template.component'; +import { EthernetSwitchesTemplateDetailsComponent } from './components/preferences/built-in/ethernet-switches/ethernet-switches-template-details/ethernet-switches-template-details.component'; +import { EthernetSwitchesTemplatesComponent } from './components/preferences/built-in/ethernet-switches/ethernet-switches-templates/ethernet-switches-templates.component'; +import { AddDockerTemplateComponent } from './components/preferences/docker/add-docker-template/add-docker-template.component'; +import { CopyDockerTemplateComponent } from './components/preferences/docker/copy-docker-template/copy-docker-template.component'; +import { DockerTemplateDetailsComponent } from './components/preferences/docker/docker-template-details/docker-template-details.component'; +import { DockerTemplatesComponent } from './components/preferences/docker/docker-templates/docker-templates.component'; +import { AddIosTemplateComponent } from './components/preferences/dynamips/add-ios-template/add-ios-template.component'; +import { CopyIosTemplateComponent } from './components/preferences/dynamips/copy-ios-template/copy-ios-template.component'; +import { IosTemplateDetailsComponent } from './components/preferences/dynamips/ios-template-details/ios-template-details.component'; +import { IosTemplatesComponent } from './components/preferences/dynamips/ios-templates/ios-templates.component'; +import { AddIouTemplateComponent } from './components/preferences/ios-on-unix/add-iou-template/add-iou-template.component'; +import { CopyIouTemplateComponent } from './components/preferences/ios-on-unix/copy-iou-template/copy-iou-template.component'; +import { IouTemplateDetailsComponent } from './components/preferences/ios-on-unix/iou-template-details/iou-template-details.component'; +import { IouTemplatesComponent } from './components/preferences/ios-on-unix/iou-templates/iou-templates.component'; +import { PreferencesComponent } from './components/preferences/preferences.component'; +import { AddQemuVmTemplateComponent } from './components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component'; +import { CopyQemuVmTemplateComponent } from './components/preferences/qemu/copy-qemu-vm-template/copy-qemu-vm-template.component'; +import { QemuVmTemplateDetailsComponent } from './components/preferences/qemu/qemu-vm-template-details/qemu-vm-template-details.component'; +import { QemuVmTemplatesComponent } from './components/preferences/qemu/qemu-vm-templates/qemu-vm-templates.component'; +import { AddVirtualBoxTemplateComponent } from './components/preferences/virtual-box/add-virtual-box-template/add-virtual-box-template.component'; +import { VirtualBoxTemplateDetailsComponent } from './components/preferences/virtual-box/virtual-box-template-details/virtual-box-template-details.component'; +import { VirtualBoxTemplatesComponent } from './components/preferences/virtual-box/virtual-box-templates/virtual-box-templates.component'; +import { AddVmwareTemplateComponent } from './components/preferences/vmware/add-vmware-template/add-vmware-template.component'; +import { VmwareTemplateDetailsComponent } from './components/preferences/vmware/vmware-template-details/vmware-template-details.component'; +import { VmwareTemplatesComponent } from './components/preferences/vmware/vmware-templates/vmware-templates.component'; +import { AddVpcsTemplateComponent } from './components/preferences/vpcs/add-vpcs-template/add-vpcs-template.component'; +import { VpcsTemplateDetailsComponent } from './components/preferences/vpcs/vpcs-template-details/vpcs-template-details.component'; +import { VpcsTemplatesComponent } from './components/preferences/vpcs/vpcs-templates/vpcs-templates.component'; +import { ProjectMapComponent } from './components/project-map/project-map.component'; +import { ProjectsComponent } from './components/projects/projects.component'; +import { ServersComponent } from './components/servers/servers.component'; +import { ConsoleComponent } from './components/settings/console/console.component'; +import { SettingsComponent } from './components/settings/settings.component'; +import { ListOfSnapshotsComponent } from './components/snapshots/list-of-snapshots/list-of-snapshots.component'; +import { SystemStatusComponent } from './components/system-status/system-status.component'; +import { WebConsoleFullWindowComponent } from './components/web-console-full-window/web-console-full-window.component'; +import { ConsoleGuard } from './guards/console-guard'; +import { LoginGuard } from './guards/login-guard'; +import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.component'; +import { ServerResolve } from './resolvers/server-resolve'; +import { UserManagementComponent } from './components/user-management/user-management.component'; +import { LoggedUserComponent } from './components/users/logged-user/logged-user.component'; +import { ImageManagerComponent } from '@components/image-manager/image-manager.component'; const routes: Routes = [ { @@ -84,11 +68,12 @@ const routes: Routes = [ {path: 'bundled', component: BundledServerFinderComponent}, {path: 'server/:server_id/login', component: LoginComponent}, {path: 'server/:server_id/loggeduser', component: LoggedUserComponent}, + {path : 'server/:server_id/image-manager', component: ImageManagerComponent}, { path: 'server/:server_id/projects', component: ProjectsComponent, canActivate: [LoginGuard], - resolve: {server: ServerResolve}, + resolve: { server: ServerResolve }, }, {path: 'help', component: HelpComponent}, {path: 'settings', component: SettingsComponent}, @@ -106,18 +91,18 @@ const routes: Routes = [ {path: 'installed-software', component: InstalledSoftwareComponent}, {path: 'server/:server_id/systemstatus', component: SystemStatusComponent, canActivate: [LoginGuard]}, - {path: 'server/:server_ip/:server_port/project/:project_id', component: DirectLinkComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_ip/:server_port/project/:project_id', component: DirectLinkComponent, canActivate: [LoginGuard] }, { path: 'server/:server_id/project/:project_id/snapshots', component: ListOfSnapshotsComponent, canActivate: [LoginGuard], - resolve: {server: ServerResolve}, + resolve: { server: ServerResolve }, }, - {path: 'server/:server_id/preferences', component: PreferencesComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_id/preferences', component: PreferencesComponent, canActivate: [LoginGuard] }, // { path: 'server/:server_id/preferences/general', component: GeneralPreferencesComponent }, - {path: 'server/:server_id/preferences/builtin', component: BuiltInPreferencesComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_id/preferences/builtin', component: BuiltInPreferencesComponent, canActivate: [LoginGuard] }, - {path: 'server/:server_id/preferences/builtin/ethernet-hubs', component: EthernetHubsTemplatesComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_id/preferences/builtin/ethernet-hubs', component: EthernetHubsTemplatesComponent, canActivate: [LoginGuard] }, { path: 'server/:server_id/preferences/builtin/ethernet-hubs/addtemplate', component: EthernetHubsAddTemplateComponent, @@ -145,7 +130,7 @@ const routes: Routes = [ canActivate: [LoginGuard] }, - {path: 'server/:server_id/preferences/builtin/cloud-nodes', component: CloudNodesTemplatesComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_id/preferences/builtin/cloud-nodes', component: CloudNodesTemplatesComponent, canActivate: [LoginGuard] }, { path: 'server/:server_id/preferences/builtin/cloud-nodes/addtemplate', component: CloudNodesAddTemplateComponent, @@ -158,13 +143,9 @@ const routes: Routes = [ }, //{ path: 'server/:server_id/preferences/dynamips', component: DynamipsPreferencesComponent }, - {path: 'server/:server_id/preferences/dynamips/templates', component: IosTemplatesComponent, canActivate: [LoginGuard]}, - {path: 'server/:server_id/preferences/dynamips/templates/addtemplate', component: AddIosTemplateComponent, canActivate: [LoginGuard]}, - { - path: 'server/:server_id/preferences/dynamips/templates/:template_id', - component: IosTemplateDetailsComponent, - canActivate: [LoginGuard] - }, + { path: 'server/:server_id/preferences/dynamips/templates', component: IosTemplatesComponent, canActivate: [LoginGuard] }, + { path: 'server/:server_id/preferences/dynamips/templates/addtemplate', component: AddIosTemplateComponent, canActivate: [LoginGuard] }, + { path: 'server/:server_id/preferences/dynamips/templates/:template_id', component: IosTemplateDetailsComponent, canActivate: [LoginGuard] }, { path: 'server/:server_id/preferences/dynamips/templates/:template_id/copy', component: CopyIosTemplateComponent, @@ -172,47 +153,39 @@ const routes: Routes = [ }, // { path: 'server/:server_id/preferences/qemu', component: QemuPreferencesComponent }, - {path: 'server/:server_id/preferences/qemu/templates', component: QemuVmTemplatesComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_id/preferences/qemu/templates', component: QemuVmTemplatesComponent, canActivate: [LoginGuard] }, { path: 'server/:server_id/preferences/qemu/templates/:template_id/copy', component: CopyQemuVmTemplateComponent, canActivate: [LoginGuard] }, - { - path: 'server/:server_id/preferences/qemu/templates/:template_id', - component: QemuVmTemplateDetailsComponent, - canActivate: [LoginGuard] - }, - {path: 'server/:server_id/preferences/qemu/addtemplate', component: AddQemuVmTemplateComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_id/preferences/qemu/templates/:template_id', component: QemuVmTemplateDetailsComponent, canActivate: [LoginGuard] }, + { path: 'server/:server_id/preferences/qemu/addtemplate', component: AddQemuVmTemplateComponent, canActivate: [LoginGuard] }, // { path: 'server/:server_id/preferences/vpcs', component: VpcsPreferencesComponent }, - {path: 'server/:server_id/preferences/vpcs/templates', component: VpcsTemplatesComponent, canActivate: [LoginGuard]}, - { - path: 'server/:server_id/preferences/vpcs/templates/:template_id', - component: VpcsTemplateDetailsComponent, - canActivate: [LoginGuard] - }, - {path: 'server/:server_id/preferences/vpcs/addtemplate', component: AddVpcsTemplateComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_id/preferences/vpcs/templates', component: VpcsTemplatesComponent, canActivate: [LoginGuard] }, + { path: 'server/:server_id/preferences/vpcs/templates/:template_id', component: VpcsTemplateDetailsComponent, canActivate: [LoginGuard] }, + { path: 'server/:server_id/preferences/vpcs/addtemplate', component: AddVpcsTemplateComponent, canActivate: [LoginGuard] }, // { path: 'server/:server_id/preferences/virtualbox', component: VirtualBoxPreferencesComponent }, - {path: 'server/:server_id/preferences/virtualbox/templates', component: VirtualBoxTemplatesComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_id/preferences/virtualbox/templates', component: VirtualBoxTemplatesComponent, canActivate: [LoginGuard] }, { path: 'server/:server_id/preferences/virtualbox/templates/:template_id', component: VirtualBoxTemplateDetailsComponent, canActivate: [LoginGuard] }, - {path: 'server/:server_id/preferences/virtualbox/addtemplate', component: AddVirtualBoxTemplateComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_id/preferences/virtualbox/addtemplate', component: AddVirtualBoxTemplateComponent, canActivate: [LoginGuard] }, // { path: 'server/:server_id/preferences/vmware', component: VmwarePreferencesComponent }, - {path: 'server/:server_id/preferences/vmware/templates', component: VmwareTemplatesComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_id/preferences/vmware/templates', component: VmwareTemplatesComponent, canActivate: [LoginGuard] }, { path: 'server/:server_id/preferences/vmware/templates/:template_id', component: VmwareTemplateDetailsComponent, canActivate: [LoginGuard] }, - {path: 'server/:server_id/preferences/vmware/addtemplate', component: AddVmwareTemplateComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_id/preferences/vmware/addtemplate', component: AddVmwareTemplateComponent, canActivate: [LoginGuard] }, - {path: 'server/:server_id/preferences/docker/templates', component: DockerTemplatesComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_id/preferences/docker/templates', component: DockerTemplatesComponent, canActivate: [LoginGuard] }, { path: 'server/:server_id/preferences/docker/templates/:template_id', component: DockerTemplateDetailsComponent, @@ -223,7 +196,7 @@ const routes: Routes = [ component: CopyDockerTemplateComponent, canActivate: [LoginGuard] }, - {path: 'server/:server_id/preferences/docker/addtemplate', component: AddDockerTemplateComponent, canActivate: [LoginGuard]}, + { path: 'server/:server_id/preferences/docker/addtemplate', component: AddDockerTemplateComponent, canActivate: [LoginGuard] }, {path: 'server/:server_id/preferences/iou/templates', component: IouTemplatesComponent, canActivate: [LoginGuard]}, {path: 'server/:server_id/preferences/iou/templates/:template_id', component: IouTemplateDetailsComponent, canActivate: [LoginGuard]}, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 905a5ca5..29eb32df 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -113,6 +113,7 @@ import { DeleteActionComponent } from './components/project-map/context-menu/act import { DuplicateActionComponent } from './components/project-map/context-menu/actions/duplicate-action/duplicate-action.component'; import { EditConfigActionComponent } from './components/project-map/context-menu/actions/edit-config/edit-config-action.component'; import { EditStyleActionComponent } from './components/project-map/context-menu/actions/edit-style-action/edit-style-action.component'; +import { EditLinkStyleActionComponent } from './components/project-map/context-menu/actions/edit-link-style-action/edit-link-style-action.component'; import { EditTextActionComponent } from './components/project-map/context-menu/actions/edit-text-action/edit-text-action.component'; import { ExportConfigActionComponent } from './components/project-map/context-menu/actions/export-config/export-config-action.component'; import { HttpConsoleNewTabActionComponent } from './components/project-map/context-menu/actions/http-console-new-tab/http-console-new-tab-action.component'; @@ -139,6 +140,7 @@ import { ContextMenuComponent } from './components/project-map/context-menu/cont import { ConfigDialogComponent } from './components/project-map/context-menu/dialogs/config-dialog/config-dialog.component'; import { DrawLinkToolComponent } from './components/project-map/draw-link-tool/draw-link-tool.component'; import { StyleEditorDialogComponent } from './components/project-map/drawings-editors/style-editor/style-editor.component'; +import { LinkStyleEditorDialogComponent } from './components/project-map/drawings-editors/link-style-editor/link-style-editor.component'; import { TextEditorDialogComponent } from './components/project-map/drawings-editors/text-editor/text-editor.component'; import { HelpDialogComponent } from './components/project-map/help-dialog/help-dialog.component'; import { NodeCreatedLabelStylesFixer } from './components/project-map/helpers/node-created-label-styles-fixer'; @@ -314,6 +316,9 @@ import { DisplayPathPipe } from './components/permissions-management/display-pat import {RolePermissionsComponent} from "@components/role-management/role-detail/role-permissions/role-permissions.component"; import { ChangeUserPasswordComponent } from './components/user-management/user-detail/change-user-password/change-user-password.component'; import {MatMenuModule} from "@angular/material/menu"; +import { ImageManagerComponent } from './components/image-manager/image-manager.component'; +import { AddImageDialogComponent } from './components/image-manager/add-image-dialog/add-image-dialog.component'; +import { DeleteAllImageFilesDialogComponent } from './components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component'; @NgModule({ declarations: [ @@ -342,6 +347,7 @@ import {MatMenuModule} from "@angular/material/menu"; MoveLayerDownActionComponent, MoveLayerUpActionComponent, EditStyleActionComponent, + EditLinkStyleActionComponent, EditTextActionComponent, DeleteActionComponent, DuplicateActionComponent, @@ -369,6 +375,7 @@ import {MatMenuModule} from "@angular/material/menu"; InterfaceLabelDraggedComponent, InstallSoftwareComponent, StyleEditorDialogComponent, + LinkStyleEditorDialogComponent, TextEditorDialogComponent, PacketFiltersDialogComponent, QemuPreferencesComponent, @@ -542,6 +549,10 @@ import {MatMenuModule} from "@angular/material/menu"; FilterCompletePipe, DisplayPathPipe, ChangeUserPasswordComponent, + ProjectReadmeComponent, + ImageManagerComponent, + AddImageDialogComponent, + DeleteAllImageFilesDialogComponent, ], imports: [ BrowserModule, @@ -694,6 +705,5 @@ import {MatMenuModule} from "@angular/material/menu"; bootstrap: [AppComponent], }) export class AppModule { - constructor(protected _googleAnalyticsService: GoogleAnalyticsService) { - } + constructor(protected _googleAnalyticsService: GoogleAnalyticsService) {} } diff --git a/src/app/cartography/cartography.module.ts b/src/app/cartography/cartography.module.ts index 7d7c813d..a900548f 100644 --- a/src/app/cartography/cartography.module.ts +++ b/src/app/cartography/cartography.module.ts @@ -5,6 +5,7 @@ import { MatMenuModule } from '@angular/material/menu'; import { ANGULAR_MAP_DECLARATIONS } from './angular-map.imports'; import { D3MapComponent } from './components/d3-map/d3-map.component'; import { DraggableSelectionComponent } from './components/draggable-selection/draggable-selection.component'; +import { LinkEditingComponent } from './components/link-editing/link-editing.component'; import { DrawingAddingComponent } from './components/drawing-adding/drawing-adding.component'; import { DrawingResizingComponent } from './components/drawing-resizing/drawing-resizing.component'; import { ExperimentalMapComponent } from './components/experimental-map/experimental-map.component'; @@ -73,6 +74,7 @@ import { SerialLinkWidget } from './widgets/links/serial-link'; SelectionControlComponent, SelectionSelectComponent, DraggableSelectionComponent, + LinkEditingComponent, MovingCanvasDirective, ZoomingCanvasDirective, ], diff --git a/src/app/cartography/components/d3-map/d3-map.component.html b/src/app/cartography/components/d3-map/d3-map.component.html index e166c697..c066bb4a 100644 --- a/src/app/cartography/components/d3-map/d3-map.component.html +++ b/src/app/cartography/components/d3-map/d3-map.component.html @@ -46,3 +46,4 @@ + diff --git a/src/app/cartography/components/link-editing/link-editing.component.html b/src/app/cartography/components/link-editing/link-editing.component.html new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/link-editing/link-editing.component.scss b/src/app/cartography/components/link-editing/link-editing.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/link-editing/link-editing.component.ts b/src/app/cartography/components/link-editing/link-editing.component.ts new file mode 100644 index 00000000..65eb2e90 --- /dev/null +++ b/src/app/cartography/components/link-editing/link-editing.component.ts @@ -0,0 +1,31 @@ +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { select } from 'd3-selection'; +import { Subscription } from 'rxjs'; +import { LinksEventSource } from '../../events/links-event-source'; +import { MapLink } from '../../models/map/map-link'; +import { LinksWidget } from '../../widgets/links'; + +@Component({ + selector: 'app-link-editing', + templateUrl: './link-editing.component.html', + styleUrls: ['./link-editing.component.scss'], +}) +export class LinkEditingComponent implements OnInit, OnDestroy { + private linkEditedSubscription: Subscription; + @Input('svg') svg: SVGSVGElement; + + constructor( + private linksWidget: LinksWidget, + private linksEventSource: LinksEventSource ) {} + + ngOnInit() { + const svg = select(this.svg); + this.linkEditedSubscription = this.linksEventSource.edited.subscribe((link: MapLink) => { + this.linksWidget.redrawLink(svg, link); + }); + } + + ngOnDestroy() { + this.linkEditedSubscription.unsubscribe(); + } +} diff --git a/src/app/cartography/converters/map/link-to-map-link-converter.ts b/src/app/cartography/converters/map/link-to-map-link-converter.ts index 28ecf2c8..14fd3827 100644 --- a/src/app/cartography/converters/map/link-to-map-link-converter.ts +++ b/src/app/cartography/converters/map/link-to-map-link-converter.ts @@ -15,6 +15,7 @@ export class LinkToMapLinkConverter implements Converter { mapLink.captureFilePath = link.capture_file_path; mapLink.capturing = link.capturing; mapLink.filters = link.filters; + mapLink.link_style = link.link_style; mapLink.linkType = link.link_type; mapLink.nodes = link.nodes.map((linkNode) => this.linkNodeToMapLinkNode.convert(linkNode, { link_id: link.link_id }) diff --git a/src/app/cartography/converters/map/map-link-to-link-converter.ts b/src/app/cartography/converters/map/map-link-to-link-converter.ts index ae269a42..6cc8be8b 100644 --- a/src/app/cartography/converters/map/map-link-to-link-converter.ts +++ b/src/app/cartography/converters/map/map-link-to-link-converter.ts @@ -16,6 +16,7 @@ export class MapLinkToLinkConverter implements Converter { link.capturing = mapLink.capturing; link.filters = mapLink.filters; link.link_type = mapLink.linkType; + link.link_style = mapLink.link_style; link.nodes = mapLink.nodes.map((mapLinkNode) => this.mapLinkNodeToMapLinkNode.convert(mapLinkNode)); link.project_id = mapLink.projectId; link.suspend = mapLink.suspend; diff --git a/src/app/cartography/events/links-event-source.ts b/src/app/cartography/events/links-event-source.ts index 53341290..7c831aa0 100644 --- a/src/app/cartography/events/links-event-source.ts +++ b/src/app/cartography/events/links-event-source.ts @@ -1,4 +1,5 @@ import { EventEmitter, Injectable } from '@angular/core'; +import { MapLink } from '../models/map/map-link'; import { MapLinkNode } from '../models/map/map-link-node'; import { DraggedDataEvent } from './event-source'; import { MapLinkCreated } from './links'; @@ -6,5 +7,6 @@ import { MapLinkCreated } from './links'; @Injectable() export class LinksEventSource { public created = new EventEmitter(); + public edited = new EventEmitter(); public interfaceDragged = new EventEmitter>(); } diff --git a/src/app/cartography/managers/graph-data-manager.ts b/src/app/cartography/managers/graph-data-manager.ts index 278e96a0..a84693c2 100644 --- a/src/app/cartography/managers/graph-data-manager.ts +++ b/src/app/cartography/managers/graph-data-manager.ts @@ -45,6 +45,7 @@ export class GraphDataManager { public setLinks(links: Link[]) { if (links) { + console.log("from set links"); const mapLinks = links.map((l) => this.linkToMapLink.convert(l)); this.mapLinksDataSource.set(mapLinks); @@ -88,6 +89,7 @@ export class GraphDataManager { private onDataUpdate() { this.layersManager.clear(); this.layersManager.setNodes(this.getNodes()); + console.log(this.getLinks()); this.layersManager.setLinks(this.getLinks()); this.layersManager.setDrawings(this.getDrawings()); } diff --git a/src/app/cartography/managers/layers-manager.ts b/src/app/cartography/managers/layers-manager.ts index f0e6007b..afb2e13e 100644 --- a/src/app/cartography/managers/layers-manager.ts +++ b/src/app/cartography/managers/layers-manager.ts @@ -38,6 +38,7 @@ export class LayersManager { } public setLinks(links: MapLink[]) { + console.log('from set links 2'); links .filter((link: MapLink) => link.source && link.target) .forEach((link: MapLink) => { diff --git a/src/app/cartography/models/map/map-link.ts b/src/app/cartography/models/map/map-link.ts index 57bac994..339f165d 100644 --- a/src/app/cartography/models/map/map-link.ts +++ b/src/app/cartography/models/map/map-link.ts @@ -2,6 +2,7 @@ import { Filter } from '../../../models/filter'; import { Indexed } from '../../datasources/map-datasource'; import { MapLinkNode } from './map-link-node'; import { MapNode } from './map-node'; +import { LinkStyle } from '../../../models/link-style'; export class MapLink implements Indexed { id: string; @@ -13,6 +14,7 @@ export class MapLink implements Indexed { nodes: MapLinkNode[]; projectId: string; suspend: boolean; + link_style?: LinkStyle; distance: number; // this is not from server length: number; // this is not from server diff --git a/src/app/cartography/models/node.ts b/src/app/cartography/models/node.ts index 01ffd58f..c1fef666 100644 --- a/src/app/cartography/models/node.ts +++ b/src/app/cartography/models/node.ts @@ -45,7 +45,6 @@ export class Properties { kernel_command_line: string; kernel_image: string; kernel_image_md5sum?: any; - legacy_networking: boolean; mac_address: string; options: string; platform: string; diff --git a/src/app/cartography/services/map-change-detector-ref.ts b/src/app/cartography/services/map-change-detector-ref.ts index c97c0df1..59c79171 100644 --- a/src/app/cartography/services/map-change-detector-ref.ts +++ b/src/app/cartography/services/map-change-detector-ref.ts @@ -7,6 +7,7 @@ export class MapChangeDetectorRef { public hasBeenDrawn = false; public detectChanges() { + console.log('from map change detector'); this.changesDetected.emit(true); } } diff --git a/src/app/cartography/widgets/interface-status.ts b/src/app/cartography/widgets/interface-status.ts index ebafddcc..6ed0e8dc 100644 --- a/src/app/cartography/widgets/interface-status.ts +++ b/src/app/cartography/widgets/interface-status.ts @@ -52,13 +52,13 @@ export class InterfaceStatusWidget implements Widget { new LinkStatus( start_point.x, start_point.y, - l.capturing && l.suspend ? 'suspended' : l.source.status, + (( !l.capturing && l.suspend) || ( l.capturing && l.suspend)) ? 'suspended' : l.source.status, sourcePort ), new LinkStatus( end_point.x, end_point.y, - l.capturing && l.suspend ? 'suspended' : l.target.status, + (( !l.capturing && l.suspend) || ( l.capturing && l.suspend)) ? 'suspended' : l.target.status, destinationPort ), ]; diff --git a/src/app/cartography/widgets/link.ts b/src/app/cartography/widgets/link.ts index 3e9a3334..6b2fd60e 100644 --- a/src/app/cartography/widgets/link.ts +++ b/src/app/cartography/widgets/link.ts @@ -22,14 +22,15 @@ export class LinkWidget implements Widget { private selectionManager: SelectionManager, private ethernetLinkWidget: EthernetLinkWidget, private serialLinkWidget: SerialLinkWidget - ) {} + ) { } public draw(view: SVGSelection) { const link_body = view.selectAll('g.link_body').data((l) => [l]); const link_body_enter = link_body.enter().append('g').attr('class', 'link_body'); - const link_body_merge = link_body.merge(link_body_enter).attr('transform', (link) => { + const link_body_merge = link_body.merge(link_body_enter) + .attr('transform', (link) => { const translation = this.multiLinkCalculatorHelper.linkTranslation(link.distance, link.source, link.target); return `translate (${translation.dx}, ${translation.dy})`; }); @@ -50,9 +51,8 @@ export class LinkWidget implements Widget { }) .attr('class', 'capture-icon') .attr('transform', (link) => { - return `translate (${(link.source.x + link.target.x) / 2 + 24}, ${ - (link.source.y + link.target.y) / 2 + 24 - }) scale(0.5)`; + return `translate (${(link.source.x + link.target.x) / 2 + 24}, ${(link.source.y + link.target.y) / 2 + 24 + }) scale(0.5)`; }) .attr('viewBox', '0 0 20 20') .append('image') @@ -74,9 +74,8 @@ export class LinkWidget implements Widget { }) .attr('class', 'filter-capture-icon') .attr('transform', (link) => { - return `translate (${(link.source.x + link.target.x) / 2 + 24}, ${ - (link.source.y + link.target.y) / 2 + 24 - }) scale(0.5)`; + return `translate (${(link.source.x + link.target.x) / 2 + 24}, ${(link.source.y + link.target.y) / 2 + 24 + }) scale(0.5)`; }) .attr('viewBox', '0 0 20 20') .append('image') @@ -99,9 +98,8 @@ export class LinkWidget implements Widget { .attr('width', '48px') .attr('height', '48px') .attr('transform', (link) => { - return `translate (${(link.source.x + link.target.x) / 2 + 24}, ${ - (link.source.y + link.target.y) / 2 + 24 - }) scale(0.5)`; + return `translate (${(link.source.x + link.target.x) / 2 + 24}, ${(link.source.y + link.target.y) / 2 + 24 + }) scale(0.5)`; }) .attr('viewBox', '0 0 20 20') .append('image') @@ -113,8 +111,7 @@ export class LinkWidget implements Widget { link_body .filter((l) => { return ( - l.capturing && - l.suspend && + ((!l.capturing && l.suspend)|| l.capturing && l.suspend) && !(l.filters.bpf || l.filters.corrupt || l.filters.delay || l.filters.frequency_drop || l.filters.packet_loss) ); }) @@ -125,9 +122,8 @@ export class LinkWidget implements Widget { }) .attr('class', 'pause-icon') .attr('transform', (link) => { - return `translate (${(link.source.x + link.target.x) / 2 + 24}, ${ - (link.source.y + link.target.y) / 2 + 24 - }) scale(0.5)`; + return `translate (${(link.source.x + link.target.x) / 2 + 24}, ${(link.source.y + link.target.y) / 2 + 24 + }) scale(0.5)`; }) .attr('viewBox', '0 0 20 20') .append('image') diff --git a/src/app/cartography/widgets/links.ts b/src/app/cartography/widgets/links.ts index f6eecce6..59addeec 100644 --- a/src/app/cartography/widgets/links.ts +++ b/src/app/cartography/widgets/links.ts @@ -11,6 +11,7 @@ export class LinksWidget implements Widget { constructor(private multiLinkCalculatorHelper: MultiLinkCalculatorHelper, private linkWidget: LinkWidget) {} public redrawLink(view: SVGSelection, link: MapLink) { + console.log('redraw called'); this.linkWidget.draw(this.selectLink(view, link)); } diff --git a/src/app/cartography/widgets/links/ethernet-link.ts b/src/app/cartography/widgets/links/ethernet-link.ts index 3de741e0..18eafa0f 100644 --- a/src/app/cartography/widgets/links/ethernet-link.ts +++ b/src/app/cartography/widgets/links/ethernet-link.ts @@ -4,21 +4,29 @@ import { LinkContextMenu } from '../../events/event-source'; import { MapLink } from '../../models/map/map-link'; import { SVGSelection } from '../../models/types'; import { Widget } from '../widget'; +import { LinkStyle } from '../../../models/link-style'; +import { StyleTranslator} from './style-translator'; class EthernetLinkPath { - constructor(public source: [number, number], public target: [number, number]) {} + constructor(public source: [number, number], public target: [number, number], public style: LinkStyle) {} } @Injectable() export class EthernetLinkWidget implements Widget { public onContextMenu = new EventEmitter(); + private defaultEthernetLinkStyle : LinkStyle = { + color: "#000", + width: 2, + type: 0 + }; constructor() {} private linktoEthernetLink(link: MapLink) { return new EthernetLinkPath( [link.source.x + link.source.width / 2, link.source.y + link.source.height / 2], - [link.target.x + link.target.width / 2, link.target.y + link.target.height / 2] + [link.target.x + link.target.width / 2, link.target.y + link.target.height / 2], + link.link_style.color ? link.link_style : this.defaultEthernetLinkStyle ); } @@ -38,15 +46,15 @@ export class EthernetLinkWidget implements Widget { let link: MapLink = (datum as unknown) as MapLink; const evt = event; this.onContextMenu.emit(new LinkContextMenu(evt, link)); - }); - - link_enter - .attr('stroke', '#000') - .attr('stroke-width', '2') - .on('contextmenu', (datum) => { - let link: MapLink = (datum as unknown) as MapLink; - const evt = event; - this.onContextMenu.emit(new LinkContextMenu(evt, link)); + }) + .attr('stroke', (datum) => { + return datum.style.color; + }) + .attr('stroke-width', (datum) => { + return datum.style.width; + }) + .attr('stroke-dasharray', (datum) => { + return StyleTranslator.getLinkStyle(datum.style); }); const link_merge = link.merge(link_enter); diff --git a/src/app/cartography/widgets/links/serial-link.ts b/src/app/cartography/widgets/links/serial-link.ts index 9fc0e921..49e8c509 100644 --- a/src/app/cartography/widgets/links/serial-link.ts +++ b/src/app/cartography/widgets/links/serial-link.ts @@ -4,19 +4,27 @@ import { LinkContextMenu } from '../../events/event-source'; import { MapLink } from '../../models/map/map-link'; import { SVGSelection } from '../../models/types'; import { Widget } from '../widget'; +import { LinkStyle } from '../../../models/link-style'; +import { StyleTranslator} from './style-translator'; class SerialLinkPath { constructor( public source: [number, number], public source_angle: [number, number], public target_angle: [number, number], - public target: [number, number] + public target: [number, number], + public style: LinkStyle ) {} } @Injectable() export class SerialLinkWidget implements Widget { public onContextMenu = new EventEmitter(); + private defaultSerialLinkStyle : LinkStyle = { + color: "#B22222", + width: 2, + type: 0 + }; constructor() {} @@ -47,7 +55,12 @@ export class SerialLinkWidget implements Widget { target.y - dy / 2.0 - 15 * vect_rot[1], ]; - return new SerialLinkPath([source.x, source.y], angle_source, angle_target, [target.x, target.y]); + return new SerialLinkPath( + [source.x, source.y], + angle_source, + angle_target, + [target.x, target.y], + link.link_style.color ? link.link_style : this.defaultSerialLinkStyle); } public draw(view: SVGSelection) { @@ -68,7 +81,16 @@ export class SerialLinkWidget implements Widget { this.onContextMenu.emit(new LinkContextMenu(evt, link)); }); - link_enter.attr('stroke', '#B22222').attr('fill', 'none').attr('stroke-width', '2'); + link_enter + .attr('stroke', (datum) => { + return datum.style.color; + }) + .attr('stroke-width', (datum) => { + return datum.style.width; + }) + .attr('stroke-dasharray', (datum) => { + return StyleTranslator.getLinkStyle(datum.style); + }); const link_merge = link.merge(link_enter); diff --git a/src/app/cartography/widgets/links/style-translator.ts b/src/app/cartography/widgets/links/style-translator.ts new file mode 100644 index 00000000..34762529 --- /dev/null +++ b/src/app/cartography/widgets/links/style-translator.ts @@ -0,0 +1,16 @@ +import { LinkStyle } from '../../../models/link-style'; + +export class StyleTranslator { + static getLinkStyle(linkStyle: LinkStyle) { + if (linkStyle.type == 1) { + return `10, 10` + } + if (linkStyle.type == 2) { + return `${linkStyle.width}, ${linkStyle.width}` + } + if (linkStyle.type == 3) { + return `20, 10, ${linkStyle.width}, ${linkStyle.width}, ${linkStyle.width}, 10` + } + return `0, 0` + } +} diff --git a/src/app/components/help/help.component.html b/src/app/components/help/help.component.html index d6f11ce5..85b7edd6 100644 --- a/src/app/components/help/help.component.html +++ b/src/app/components/help/help.component.html @@ -21,7 +21,8 @@ Third party components -
+ +
diff --git a/src/app/components/help/help.component.scss b/src/app/components/help/help.component.scss index 6e0e106f..a74bf126 100644 --- a/src/app/components/help/help.component.scss +++ b/src/app/components/help/help.component.scss @@ -2,3 +2,10 @@ width: 100%; margin-top: 20px; } +a { + color: #f8f9fa; + font-family: Roboto, "Helvetica Neue", sans-serif; + font-size: 14px; + font-weight: 400; + text-decoration: none; +} diff --git a/src/app/components/help/help.component.ts b/src/app/components/help/help.component.ts index 57d9d1b2..285fed87 100644 --- a/src/app/components/help/help.component.ts +++ b/src/app/components/help/help.component.ts @@ -19,7 +19,7 @@ export class HelpComponent implements OnInit { }, (error) => { if (error.status === 404) { - this.thirdpartylicenses = 'File not found'; + this.thirdpartylicenses = 'Download Solar-PuTTY'; } } ); diff --git a/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.html b/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.html new file mode 100644 index 00000000..1fef1ff4 --- /dev/null +++ b/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.html @@ -0,0 +1,70 @@ +
+
+
+
Would you like to automatically install appliances for this image?
+
+
+ +
+
+ +
+ + + Yes + + + No + + +
+
+ +
+
+ + + + +
+
+
+
Please Select image
+ +
+
+ + +
+
+ +
+
+
+ {{i+1}}. {{img?.name}} +
+
+
+
+ + +
+
+
+
+
+
+
+

Uploaded image details

+

{{i+1}}. {{uploadFile?.filename ?? uploadFile?.error?.message}}

+
+
+
+ +
+
\ No newline at end of file diff --git a/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.scss b/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.scss new file mode 100644 index 00000000..26141499 --- /dev/null +++ b/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.scss @@ -0,0 +1,83 @@ +.progress-bar { + padding: 0; + } + .progress { + width: 50px; + background-color: #263238; + height: 28px; + margin-left: 13px; + } + .mat-input-element { + font-size: medium; + font-weight: 200; + } + #fileInput { + position: absolute; + cursor: pointer; + z-index: 10; + opacity: 0; + height: 100%; + left: 0px; + top: 0px; + } + .mat-toolbar-single-row { + height: auto; + background: transparent; + padding: 0; + } + .mat-toolbar-single-row button { + width: 100px; + } + .mat-form-field { + width: 100%; + } + .message { + background-color: #ddd; + padding: 15px; + color: #333; + border: #aaa solid 1px; + border-radius: 4px; + margin: 15px 0; + } + .preview { + max-width: 200px; + vertical-align: middle; + } + .list-card { + margin-top: 20px; + } + .list-item { + margin-bottom: 20px; + } + + .non-visible { + display: none; + } + + mat-progress-bar{ + margin-top: 10px; + } + .txt-align{ + text-align: end; + } + .uploaded-text{ + color: #0ca8c7; + font-size: 17px; + } + .uploaded-error-text{ + color: #d52435; + font-weight: 600; + } + + .choose-instal-appliance { + display: flex; + flex-direction: row; + margin: 15px 0px 11px 0px; + align-items: flex-start; + } + + .instal-appliances-button { + margin: 11px; + } + + \ No newline at end of file diff --git a/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.spec.ts b/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.spec.ts new file mode 100644 index 00000000..cf51111b --- /dev/null +++ b/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.spec.ts @@ -0,0 +1,64 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialog, MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { ImageManagerService } from 'app/services/image-manager.service'; +import { ServerService } from '../../../services/server.service'; +import { MockedServerService } from '../../../services/server.service.spec'; +import { of } from 'rxjs'; +import { Server } from '../../../models/server'; + +import { AddImageDialogComponent } from './add-image-dialog.component'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ToasterService } from 'app/services/toaster.service'; +import { MockedToasterService } from 'app/services/toaster.service.spec'; + +export class MockedImageManagerService { + public getImages(server: Server) { + return of(); + } + +} + +describe('AddImageDialogComponent', () => { + let component: AddImageDialogComponent; + let fixture: ComponentFixture; + + let mockedServerService = new MockedServerService(); + let mockedImageManagerService = new MockedImageManagerService() + let mockedToasterService = new MockedToasterService() + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports:[ + MatIconModule, + MatToolbarModule, + MatMenuModule, + MatCheckboxModule, + MatDialogModule + ], + providers: [ + { provide: ServerService, useValue: mockedServerService }, + { provide: ImageManagerService, useValue: mockedImageManagerService }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: MatDialogRef, useValue: {} }, + { provide: ToasterService, useValue: mockedToasterService }, + ], + declarations: [ AddImageDialogComponent ], + schemas: [NO_ERRORS_SCHEMA], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AddImageDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.ts b/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.ts new file mode 100644 index 00000000..456e23d5 --- /dev/null +++ b/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.ts @@ -0,0 +1,67 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { animate, state, style, transition, trigger } from '@angular/animations'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { Server } from '../../../models/server'; +import { ImageManagerService } from '../../../services/image-manager.service'; +import { Observable, of } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { ImageData } from '../../../models/images'; + +@Component({ + selector: 'app-add-image-dialog', + templateUrl: './add-image-dialog.component.html', + styleUrls: ['./add-image-dialog.component.scss'], + animations: [ + trigger('detailExpand', [ + state('collapsed', style({ height: '0px', minHeight: '0', visibility: 'hidden' })), + state('expanded', style({ height: '*', visibility: 'visible' })), + transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), + ]), + ], +}) + +export class AddImageDialogComponent implements OnInit { + server: Server; + uploadedFile: boolean = false; + isExistImage: boolean = false; + isInstallAppliance: boolean = false + install_appliance: boolean = false + selectFile: any = []; + uploadFileMessage: ImageData = [] + + constructor( + @Inject(MAT_DIALOG_DATA) public data: any, + public dialogRef: MatDialogRef, + private imageService: ImageManagerService, + + ) { } + + public ngOnInit(): void { + this.server = this.data + } + + selectInstallApplianceOption(ev) { + this.install_appliance = ev.value + } + + async uploadImageFile(event) { + for (let imgFile of event.target.files) { + this.selectFile.push(imgFile) + } + await this.upload() + } + + // files uploading + upload() { + const calls = []; + this.uploadedFile = true; + this.selectFile.forEach(imgElement => { + calls.push(this.imageService.uploadedImage(this.server, this.install_appliance, imgElement.name, imgElement).pipe(catchError(error => of(error)))) + }); + Observable.forkJoin(calls).subscribe(responses => { + this.uploadFileMessage = responses + this.uploadedFile = false; + this.isExistImage = true; + }); + } +} diff --git a/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.html b/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.html new file mode 100644 index 00000000..72a4d605 --- /dev/null +++ b/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.html @@ -0,0 +1,34 @@ +
+

Do you want delete all files ?.

+
+

Your selected files

+

{{i+1}}. {{file?.filename}}

+
+
+ + +
+
+
+

Please wait.

+ +
+ + +
+
+ +
+
+
+
Images can't be deleted because image used in one or more template.
+

{{i+1}}. {{message?.error?.message}}

+
+
+
{{fileNotDeleted.length}} Images deleted successfully.
+
+
+
+ +
+
\ No newline at end of file diff --git a/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.scss b/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.scss new file mode 100644 index 00000000..f962e55b --- /dev/null +++ b/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.scss @@ -0,0 +1,11 @@ +.delete-text{ + color: #0ca8c7; + font-size: 17px; + } + +.deleted-error-text{ + color: #d52435; + font-weight: 600; +} + + \ No newline at end of file diff --git a/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.spec.ts b/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.spec.ts new file mode 100644 index 00000000..43aea89c --- /dev/null +++ b/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.spec.ts @@ -0,0 +1,65 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { ToasterService } from 'app/services/toaster.service'; +import { MockedToasterService } from 'app/services/toaster.service.spec'; +import { Server } from 'http'; +import { of } from 'rxjs'; +import { ImageManagerService } from '../../../services/image-manager.service'; +import { ServerService } from '../../../services/server.service'; +import { MockedServerService } from '../../../services/server.service.spec'; +import { ImageManagerComponent } from '../image-manager.component'; + +import { DeleteAllImageFilesDialogComponent } from './deleteallfiles-dialog.component'; + +export class MockedImageManagerService { + public deleteALLFile(server: Server, image_path) { + return of(); + } +} + + describe('DeleteAllImageFilesDialogComponent', () => { + let component: DeleteAllImageFilesDialogComponent; + let fixture: ComponentFixture; + let mockedServerService = new MockedServerService(); + let mockedImageManagerService = new MockedImageManagerService() + let mockedToasterService = new MockedToasterService() + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + MatIconModule, + MatToolbarModule, + MatMenuModule, + MatCheckboxModule, + MatDialogModule, + ], + providers: [ + { provide: ServerService, useValue: mockedServerService }, + { provide: ImageManagerService, useValue: mockedImageManagerService }, + { provide: MAT_DIALOG_DATA, useValue: {} }, + { provide: MatDialogRef, useValue: {} }, + { provide: ToasterService, useValue: mockedToasterService }, + + ], + declarations: [DeleteAllImageFilesDialogComponent, + ], + schemas: [NO_ERRORS_SCHEMA], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DeleteAllImageFilesDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.ts b/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.ts new file mode 100644 index 00000000..6fd33fca --- /dev/null +++ b/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.ts @@ -0,0 +1,50 @@ +import { Component, Inject, OnInit } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { ImageManagerService } from '../../../services/image-manager.service'; +import { ToasterService } from '../../../services/toaster.service'; +import { Observable, of } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { ImageData } from '../../../models/images'; + +@Component({ + selector: 'app-deleteallfiles-dialog', + templateUrl: './deleteallfiles-dialog.component.html', + styleUrls: ['./deleteallfiles-dialog.component.scss'] +}) +export class DeleteAllImageFilesDialogComponent implements OnInit { + isDelete: boolean = false; + isUsedFiles: boolean = false; + deleteFliesDetails: ImageData = [] + fileNotDeleted: ImageData = [] + + constructor( + @Inject(MAT_DIALOG_DATA) public deleteData: any, + public dialogRef: MatDialogRef, + private imageService: ImageManagerService, + private toasterService: ToasterService + ) { } + + ngOnInit(): void { + } + + async deleteAll() { + this.isDelete = true + await this.deleteFile() + } + + deleteFile() { + const calls = []; + this.deleteData.deleteFilesPaths.forEach(pathElement => { + calls.push(this.imageService.deleteFile(this.deleteData.server, pathElement.filename).pipe(catchError(error => of(error)))) + }); + Observable.forkJoin(calls).subscribe(responses => { + this.deleteFliesDetails = responses.filter(x => x !== null) + this.fileNotDeleted = responses.filter(x => x === null) + this.isUsedFiles = true; + this.isDelete = true + }); + + } + + +} diff --git a/src/app/components/image-manager/image-database-file.ts b/src/app/components/image-manager/image-database-file.ts new file mode 100644 index 00000000..09415d7b --- /dev/null +++ b/src/app/components/image-manager/image-database-file.ts @@ -0,0 +1,36 @@ + + +import { DataSource, SelectionModel } from '@angular/cdk/collections'; +import { MatSort } from '@angular/material/sort'; +import { BehaviorSubject, Observable, Subscription, merge } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { Image } from '../../models/images'; + + +export class imageDatabase { + dataChange: BehaviorSubject = new BehaviorSubject([]); + get data(): Image[] { + return this.dataChange.value; + } + + public addImages(fileData: Image[]) { + this.dataChange.next(fileData); + } + +} + +export class imageDataSource extends DataSource { + constructor(private serverDatabase: imageDatabase) { + super(); + } + + connect(): Observable { + return merge(this.serverDatabase.dataChange).pipe( + map(() => { + return this.serverDatabase.data; + }) + ); + } + + disconnect() { } +} diff --git a/src/app/components/image-manager/image-manager.component.html b/src/app/components/image-manager/image-manager.component.html new file mode 100644 index 00000000..5dde19d3 --- /dev/null +++ b/src/app/components/image-manager/image-manager.component.html @@ -0,0 +1,70 @@ +
+
+
+
+

Image Manager

+
+
+ +
+
+ + +
+
+ + +
+ + + + + + + + + + + + + + + File Name + + {{ row.filename }} + + + + + Image Type + {{ row.image_type }} + + + + Image Size + {{ (row.image_size/1000000).toFixed()}} MB + + + + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/src/app/components/image-manager/image-manager.component.scss b/src/app/components/image-manager/image-manager.component.scss new file mode 100644 index 00000000..047a0ad8 --- /dev/null +++ b/src/app/components/image-manager/image-manager.component.scss @@ -0,0 +1,17 @@ +.non-visible { + display: none; + } + +.img-btn{ + margin: auto; + } +.btn-box{ + display: flex; + margin-top: 10px; +} + +mat-header-cell, mat-cell { + justify-content: center; +} + + \ No newline at end of file diff --git a/src/app/components/image-manager/image-manager.component.spec.ts b/src/app/components/image-manager/image-manager.component.spec.ts new file mode 100644 index 00000000..bdba7db1 --- /dev/null +++ b/src/app/components/image-manager/image-manager.component.spec.ts @@ -0,0 +1,96 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { MatCheckboxModule } from '@angular/material/checkbox'; +import { MatDialogModule } from '@angular/material/dialog'; +import { MatIconModule } from '@angular/material/icon'; +import { MatMenuModule } from '@angular/material/menu'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { ImageManagerService } from 'app/services/image-manager.service'; +import { ServerService } from 'app/services/server.service'; +import { MockedServerService } from 'app/services/server.service.spec'; +import { of } from 'rxjs'; +import { Server } from '../../models/server'; + +import { ImageManagerComponent } from './image-manager.component'; +import { Image } from '../../models/images'; +import { ProgressService } from 'app/common/progress/progress.service'; +import { MockedProgressService } from '../project-map/project-map.component.spec'; +import { MockedActivatedRoute } from '../preferences/preferences.component.spec'; +import { ActivatedRoute } from '@angular/router'; +import { MockedVersionService } from '../../services/version.service.spec'; +import { VersionService } from 'app/services/version.service'; +import { ToasterService } from 'app/services/toaster.service'; +import { MockedToasterService } from 'app/services/toaster.service.spec'; + +export class MockedImageManagerService { + public getImages(server: Server) { + return of(); + } + + public deleteFile(server: Server, image_path) { + return of(); + } + +} + +describe('ImageManagerComponent', () => { + let component: ImageManagerComponent; + let fixture: ComponentFixture; + + let mockedServerService = new MockedServerService(); + let mockedImageManagerService = new MockedImageManagerService() + let mockedProgressService = new MockedProgressService() + let mockedVersionService = new MockedVersionService() + let mockedToasterService = new MockedToasterService() + let activatedRoute = new MockedActivatedRoute().get(); + + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ + MatIconModule, + MatToolbarModule, + MatMenuModule, + MatCheckboxModule, + MatDialogModule + ], + providers: [ + { + provide: ActivatedRoute, + useValue: activatedRoute, + }, + { provide: ServerService, useValue: mockedServerService }, + { provide: ImageManagerService, useValue: mockedImageManagerService }, + { provide: ProgressService, useValue: mockedProgressService }, + { provide: VersionService, useValue: mockedVersionService }, + { provide: ToasterService, useValue: mockedToasterService }, + ], + declarations: [ImageManagerComponent], + schemas: [NO_ERRORS_SCHEMA], + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ImageManagerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + + it('should call save images', () => { + spyOn(mockedImageManagerService, 'getImages').and.returnValue(of({} as Image)); + component.getImages() + expect(mockedImageManagerService.getImages).toHaveBeenCalled(); + }); + + it('should delete image', () => { + spyOn(mockedImageManagerService, 'deleteFile').and.returnValue(of({} as Image)); + component.deleteFile('image_path') + expect(mockedImageManagerService.deleteFile).toHaveBeenCalled(); + }); +}); diff --git a/src/app/components/image-manager/image-manager.component.ts b/src/app/components/image-manager/image-manager.component.ts new file mode 100644 index 00000000..ee51888f --- /dev/null +++ b/src/app/components/image-manager/image-manager.component.ts @@ -0,0 +1,149 @@ +import { Component, OnInit } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { ServerService } from '../../services/server.service'; +import { VersionService } from '../../services/version.service'; +import { ProgressService } from 'app/common/progress/progress.service'; +import { Image } from '../../models/images'; +import { Server } from '../../models/server'; +import { ImageManagerService } from "../../services/image-manager.service"; +import { DataSource, SelectionModel } from '@angular/cdk/collections'; +import { AddImageDialogComponent } from './add-image-dialog/add-image-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; +import { ToasterService } from '../../services/toaster.service'; +import { DeleteAllImageFilesDialogComponent } from './deleteallfiles-dialog/deleteallfiles-dialog.component'; +import { imageDataSource, imageDatabase } from "./image-database-file"; + +@Component({ + selector: 'app-image-manager', + templateUrl: './image-manager.component.html', + styleUrls: ['./image-manager.component.scss'] +}) +export class ImageManagerComponent implements OnInit { + server: Server; + public version: string; + dataSource: imageDataSource; + imageDatabase = new imageDatabase(); + isAllDelete: boolean = false + selection = new SelectionModel(true, []); + + displayedColumns = ['select', 'filename', 'image_type', 'image_size','delete']; + + constructor( + private imageService: ImageManagerService, + private progressService: ProgressService, + private route: ActivatedRoute, + private serverService: ServerService, + private versionService: VersionService, + private dialog: MatDialog, + private toasterService: ToasterService, + + ) { } + + ngOnInit(): void { + let server_id = parseInt(this.route.snapshot.paramMap.get('server_id')); + this.serverService.get(server_id).then((server: Server) => { + this.server = server; + if (server.authToken) { + this.getImages() + } + // this.versionService.get(this.server).subscribe((version: Version) => { + // this.version = version.version; + // }); + }); + this.dataSource = new imageDataSource(this.imageDatabase); + } + + getImages() { + this.imageService.getImages(this.server).subscribe( + (images: Image[]) => { + this.imageDatabase.addImages(images) + }, + (error) => { + this.toasterService.error(error.error.message) + + } + ); + } + + deleteFile(path) { + this.imageService.deleteFile(this.server, path).subscribe( + (res) => { + this.getImages() + this.unChecked() + this.toasterService.success('File deleted'); + }, + (error) => { + this.getImages() + this.unChecked() + this.toasterService.error(error.error.message) + } + ); + } + + isAllSelected() { + const numSelected = this.selection.selected.length; + const numRows = this.imageDatabase.data.length; + return numSelected === numRows; + } + + selectAllImages() { + this.isAllSelected() ? this.unChecked() : this.allChecked() + } + + unChecked() { + this.selection.clear() + this.isAllDelete = false + } + + allChecked() { + this.imageDatabase.data.forEach(row => this.selection.select(row)) + this.isAllDelete = true; + } + + public addImageDialog() { + const dialogRef = this.dialog.open(AddImageDialogComponent, { + width: '600px', + maxHeight: '550px', + autoFocus: false, + disableClose: true, + data: this.server + }); + + dialogRef.afterClosed().subscribe((isAddes: boolean) => { + if (isAddes) { + this.getImages() + this.unChecked() + } else { + this.getImages() + this.unChecked() + return false; + } + }); + } + + deleteAllFiles() { + const dialogRef = this.dialog.open(DeleteAllImageFilesDialogComponent, { + width: '500px', + maxHeight: '650px', + autoFocus: false, + disableClose: true, + data: { + server: this.server, + deleteFilesPaths: this.selection.selected + } + }); + + dialogRef.afterClosed().subscribe((isAllfilesdeleted: boolean) => { + if (isAllfilesdeleted) { + this.unChecked() + this.getImages() + this.toasterService.success('All files deleted'); + } else { + this.unChecked() + this.getImages() + return false; + } + }); + } +} + diff --git a/src/app/components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component.html b/src/app/components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component.html index 0aa2d7fe..e6df8fb1 100644 --- a/src/app/components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component.html +++ b/src/app/components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component.html @@ -29,16 +29,16 @@ - +
- - {{ binary.path }} + + {{ platform }}
diff --git a/src/app/components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component.ts b/src/app/components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component.ts index 464a4492..661866ba 100644 --- a/src/app/components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component.ts +++ b/src/app/components/preferences/qemu/add-qemu-vm-template/add-qemu-vm-template.component.ts @@ -1,7 +1,7 @@ import { Component, OnInit } from '@angular/core'; import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; -import { FileItem, FileUploader, ParsedResponseHeaders } from 'ng2-file-upload'; +import { FileItem, FileUploader, FileUploaderOptions, ParsedResponseHeaders } from 'ng2-file-upload'; import { v4 as uuid } from 'uuid'; import { Compute } from '../../../../models/compute'; import { QemuBinary } from '../../../../models/qemu/qemu-binary'; @@ -23,6 +23,8 @@ import { ToasterService } from '../../../../services/toaster.service'; export class AddQemuVmTemplateComponent implements OnInit { server: Server; qemuBinaries: QemuBinary[] = []; + selectPlatform: string[] = []; + selectedPlatform: string; selectedBinary: QemuBinary; ramMemory: number; consoleTypes: string[] = []; @@ -68,6 +70,7 @@ export class AddQemuVmTemplateComponent implements OnInit { ngOnInit() { this.uploader = new FileUploader({}); + this.uploader.onAfterAddingFile = (file) => { file.withCredentials = false; }; @@ -85,6 +88,7 @@ export class AddQemuVmTemplateComponent implements OnInit { }); this.toasterService.success('Image uploaded'); }; + this.uploader.onProgressItem = (progress: any) => { this.uploadProgress = progress['progress']; }; @@ -97,17 +101,22 @@ export class AddQemuVmTemplateComponent implements OnInit { this.qemuTemplate = qemuTemplate; }); - this.qemuService.getBinaries(server).subscribe((qemuBinaries: QemuBinary[]) => { + + this.qemuService.getBinaries(this.server).subscribe((qemuBinaries: QemuBinary[]) => { this.qemuBinaries = qemuBinaries; if (this.qemuBinaries[0]) this.selectedBinary = this.qemuBinaries[0]; }); - this.qemuService.getImages(server).subscribe((qemuImages: QemuImage[]) => { + this.qemuService.getImages(this.server).subscribe((qemuImages: QemuImage[]) => { this.qemuImages = qemuImages; }); + this.selectPlatform = this.configurationService.getPlatform(); + this.selectedPlatform = this.selectPlatform[0]; + this.consoleTypes = this.configurationService.getConsoleTypes(); }); + } setServerType(serverType: string) { @@ -121,17 +130,19 @@ export class AddQemuVmTemplateComponent implements OnInit { } uploadImageFile(event) { + this.uploadedFile = true; let name = event.target.files[0].name; this.diskForm.controls['fileName'].setValue(name); const url = this.qemuService.getImagePath(this.server, name); this.uploader.queue.forEach((elem) => (elem.url = url)); - + const itemToUpload = this.uploader.queue[0]; - if ((itemToUpload as any).options) (itemToUpload as any).options.disableMultipart = true; - + + if ((itemToUpload as any).options) (itemToUpload as any).options.disableMultipart = true; ((itemToUpload as any).options.headers =[{name:'Authorization',value:'Bearer ' + this.server.authToken}]) this.uploader.uploadItem(itemToUpload); + } goBack() { @@ -139,9 +150,12 @@ export class AddQemuVmTemplateComponent implements OnInit { } addTemplate() { + debugger if (!this.nameForm.invalid && !this.memoryForm.invalid && (this.selectedImage || this.chosenImage)) { this.qemuTemplate.ram = +this.memoryForm.get('ramMemory').value; this.qemuTemplate.qemu_path = this.selectedBinary.path; + this.qemuTemplate.platform = this.selectedPlatform; + if (this.newImageSelected) { this.qemuTemplate.hda_disk_image = this.diskForm.get('fileName').value; } else { diff --git a/src/app/components/preferences/qemu/qemu-vm-template-details/qemu-vm-template-details.component.html b/src/app/components/preferences/qemu/qemu-vm-template-details/qemu-vm-template-details.component.html index a53f2b4e..0bcaae1f 100644 --- a/src/app/components/preferences/qemu/qemu-vm-template-details/qemu-vm-template-details.component.html +++ b/src/app/components/preferences/qemu/qemu-vm-template-details/qemu-vm-template-details.component.html @@ -192,7 +192,6 @@
- Use the legacy networking mode diff --git a/src/app/components/project-map/context-menu/actions/edit-link-style-action/edit-link-style-action.component.html b/src/app/components/project-map/context-menu/actions/edit-link-style-action/edit-link-style-action.component.html new file mode 100644 index 00000000..43ab8ded --- /dev/null +++ b/src/app/components/project-map/context-menu/actions/edit-link-style-action/edit-link-style-action.component.html @@ -0,0 +1,4 @@ + diff --git a/src/app/components/project-map/context-menu/actions/edit-link-style-action/edit-link-style-action.component.ts b/src/app/components/project-map/context-menu/actions/edit-link-style-action/edit-link-style-action.component.ts new file mode 100644 index 00000000..de5ea91b --- /dev/null +++ b/src/app/components/project-map/context-menu/actions/edit-link-style-action/edit-link-style-action.component.ts @@ -0,0 +1,33 @@ +import { Component, Input, OnChanges } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { Link } from '../../../../../models/link'; +import { Project } from '../../../../../models/project'; +import { Server } from '../../../../../models/server'; +import { LinkStyleEditorDialogComponent } from '../../../drawings-editors/link-style-editor/link-style-editor.component'; + +@Component({ + selector: 'app-edit-link-style-action', + + templateUrl: './edit-link-style-action.component.html', +}) +export class EditLinkStyleActionComponent implements OnChanges { + @Input() server: Server; + @Input() project: Project; + @Input() link: Link; + + constructor(private dialog: MatDialog) {} + + ngOnChanges() {} + + editStyle() { + const dialogRef = this.dialog.open(LinkStyleEditorDialogComponent, { + width: '800px', + autoFocus: false, + disableClose: true, + }); + let instance = dialogRef.componentInstance; + instance.server = this.server; + instance.project = this.project; + instance.link = this.link; + } +} diff --git a/src/app/components/project-map/context-menu/context-menu.component.html b/src/app/components/project-map/context-menu/context-menu.component.html index ac888f82..85f6fec9 100644 --- a/src/app/components/project-map/context-menu/context-menu.component.html +++ b/src/app/components/project-map/context-menu/context-menu.component.html @@ -180,6 +180,18 @@ [server]="server" [link]="links[0]" > + Style editor + + + +
+ + +
diff --git a/src/app/components/project-map/drawings-editors/link-style-editor/link-style-editor.component.scss b/src/app/components/project-map/drawings-editors/link-style-editor/link-style-editor.component.scss new file mode 100644 index 00000000..38ca9c48 --- /dev/null +++ b/src/app/components/project-map/drawings-editors/link-style-editor/link-style-editor.component.scss @@ -0,0 +1,54 @@ + .item { + height: 25px; + font-size: 10pt; + margin-bottom: 10px; + } + + .item-name { + margin-bottom: 10px; + } + + .item-value { + width: 100%; + margin-bottom: 10px; + } + + .input-color { + padding: 0px; + border-width: 0px; + width: 100%; + background-color: transparent; + outline: none; + } + + input:focus { + outline: none; + } + + input[type='color'] { + -webkit-appearance: none; + border: none; + height: 25px; + } + + input[type='color']::-webkit-color-swatch-wrapper { + padding: 0; + } + + input[type='color']::-webkit-color-swatch { + border: none; + } + + .modal-form-container { + display: flex; + flex-direction: column; + } + + .modal-form-container > * { + width: 100%; + } + + .form-field { + width: 100%; + } + \ No newline at end of file diff --git a/src/app/components/project-map/drawings-editors/link-style-editor/link-style-editor.component.ts b/src/app/components/project-map/drawings-editors/link-style-editor/link-style-editor.component.ts new file mode 100644 index 00000000..fca658ea --- /dev/null +++ b/src/app/components/project-map/drawings-editors/link-style-editor/link-style-editor.component.ts @@ -0,0 +1,83 @@ +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { MatDialogRef } from '@angular/material/dialog'; +import { Link } from '../../../../models/link'; +import { Project } from '../../../../models/project'; +import { Server } from '../../../../models/server'; +import { ToasterService } from '../../../../services/toaster.service'; +import { NonNegativeValidator } from '../../../../validators/non-negative-validator'; +import { LinkService } from '../../../../services/link.service'; +import { LinksDataSource } from '../../../../cartography/datasources/links-datasource'; +import { LinksEventSource } from '../../../../cartography/events/links-event-source'; +import { LinkToMapLinkConverter } from '../../../../cartography/converters/map/link-to-map-link-converter'; + +@Component({ + selector: 'app-link-style-editor', + templateUrl: './link-style-editor.component.html', + styleUrls: ['./link-style-editor.component.scss'], +}) +export class LinkStyleEditorDialogComponent implements OnInit { + server: Server; + project: Project; + link: Link; + formGroup: FormGroup; + borderTypes = ["Solid", "Dash", "Dot", "Dash Dot"]; + + constructor( + public dialogRef: MatDialogRef, + private formBuilder: FormBuilder, + private toasterService: ToasterService, + private linkService: LinkService, + private linksDataSource: LinksDataSource, + private linksEventSource: LinksEventSource, + private linkToMapLink: LinkToMapLinkConverter, + private nonNegativeValidator: NonNegativeValidator + ) { + this.formGroup = this.formBuilder.group({ + color: new FormControl('', [Validators.required]), + width: new FormControl('', [Validators.required, nonNegativeValidator.get]), + type: new FormControl('', [Validators.required]) + }); + } + + ngOnInit() { + if (!this.link.link_style?.color) { + this.formGroup.controls['color'].setValue("#000000"); + } else { + this.formGroup.controls['color'].setValue(this.link.link_style.color); + } + + this.formGroup.controls['width'].setValue(this.link.link_style.width); + + let type = this.borderTypes[0]; + if (this.link.link_style?.type) { + type = this.borderTypes[this.link.link_style.type]; + } + this.formGroup.controls['type'].setValue(type); + } + + onNoClick() { + this.dialogRef.close(); + } + + onYesClick() { + if (this.formGroup.valid) { + this.link.link_style.color = this.formGroup.get('color').value; + this.link.link_style.width = this.formGroup.get('width').value; + + let type = this.borderTypes.indexOf(this.formGroup.get('type').value); + this.link.link_style.type = type; + + this.linkService.updateLinkStyle(this.server, this.link).subscribe((link) => { + this.linksDataSource.update(link); + this.linksEventSource.edited.next(this.linkToMapLink.convert(link)); + location.reload() + // we add this code/line for reload the entire page because single graph/link style is not updated automatically. + // this.toasterService.success("Link updated"); + this.dialogRef.close(); + }); + } else { + this.toasterService.error(`Entered data is incorrect`); + } + } +} diff --git a/src/app/components/project-map/new-template-dialog/new-template-dialog.component.html b/src/app/components/project-map/new-template-dialog/new-template-dialog.component.html index ca844bae..faef1dc5 100644 --- a/src/app/components/project-map/new-template-dialog/new-template-dialog.component.html +++ b/src/app/components/project-map/new-template-dialog/new-template-dialog.component.html @@ -129,25 +129,8 @@ {{ secondActionTitle }} - - Please configure GNS3 VM to install selected appliance - - - +
-
- Server type
- - Install the appliance locally - -
Qemu binary
-
- Server type
- - Install the appliance locally - -
+
-
- Server type
- - Install the appliance locally - -
Install required files
-
- Use the legacy networking mode - +

Replicate network connection state diff --git a/src/app/components/project-map/project-map.component.html b/src/app/components/project-map/project-map.component.html index 000f83de..fa9a4852 100644 --- a/src/app/components/project-map/project-map.component.html +++ b/src/app/components/project-map/project-map.component.html @@ -103,6 +103,10 @@ settings Go to settings +