mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-04-09 11:31:15 +00:00
Merge remote-tracking branch 'upstream/master-3.0' into master-3.0
# Conflicts: # src/app/app-routing.module.ts # src/app/app.module.ts
This commit is contained in:
commit
a074bbc617
@ -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]},
|
||||
|
@ -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) {}
|
||||
}
|
||||
|
@ -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,
|
||||
],
|
||||
|
@ -46,3 +46,4 @@
|
||||
<app-selection-select></app-selection-select>
|
||||
<app-text-editor #textEditor [server]="server" [svg]="svg"></app-text-editor>
|
||||
<app-draggable-selection [svg]="svg"></app-draggable-selection>
|
||||
<app-link-editing [svg]="svg"></app-link-editing>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@ -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();
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@ export class LinkToMapLinkConverter implements Converter<Link, MapLink> {
|
||||
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 })
|
||||
|
@ -16,6 +16,7 @@ export class MapLinkToLinkConverter implements Converter<MapLink, Link> {
|
||||
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;
|
||||
|
@ -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<MapLinkCreated>();
|
||||
public edited = new EventEmitter<MapLink>();
|
||||
public interfaceDragged = new EventEmitter<DraggedDataEvent<MapLinkNode>>();
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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) => {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -7,6 +7,7 @@ export class MapChangeDetectorRef {
|
||||
public hasBeenDrawn = false;
|
||||
|
||||
public detectChanges() {
|
||||
console.log('from map change detector');
|
||||
this.changesDetected.emit(true);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
),
|
||||
];
|
||||
|
@ -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<SVGGElement, MapLink>('g.link_body').data((l) => [l]);
|
||||
|
||||
const link_body_enter = link_body.enter().append<SVGGElement>('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<SVGImageElement>('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<SVGImageElement>('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<SVGImageElement>('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<SVGImageElement>('image')
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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<LinkContextMenu>();
|
||||
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);
|
||||
|
@ -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<LinkContextMenu>();
|
||||
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);
|
||||
|
||||
|
16
src/app/cartography/widgets/links/style-translator.ts
Normal file
16
src/app/cartography/widgets/links/style-translator.ts
Normal file
@ -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`
|
||||
}
|
||||
}
|
@ -21,7 +21,8 @@
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title> Third party components </mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<div [innerHTML]="thirdpartylicenses"></div>
|
||||
<a href="https://downloads.solarwinds.com/solarwinds/GNS3/Solar-PuTTY/Solar-PuTTY-Optional.exe">
|
||||
<div [innerHTML]="thirdpartylicenses" ></div></a>
|
||||
</mat-expansion-panel>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ export class HelpComponent implements OnInit {
|
||||
},
|
||||
(error) => {
|
||||
if (error.status === 404) {
|
||||
this.thirdpartylicenses = 'File not found';
|
||||
this.thirdpartylicenses = 'Download Solar-PuTTY';
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -0,0 +1,70 @@
|
||||
<div *ngIf="!isInstallAppliance">
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<h5>Would you like to automatically install appliances for this image?</h5>
|
||||
</div>
|
||||
<div class="col-md-2 txt-align">
|
||||
<button mat-button (click)="dialogRef.close()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div mat-dialog-content>
|
||||
<mat-radio-group name="install_appliances" class="choose-instal-appliance" [value]="install_appliance" (change)="selectInstallApplianceOption($event)">
|
||||
<mat-radio-button value="true" class="instal-appliances-button" [checked]="install_appliance">
|
||||
Yes
|
||||
</mat-radio-button>
|
||||
<mat-radio-button value="false" class="instal-appliances-button" [checked]="!install_appliance">
|
||||
No
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-raised-button color="primary" (click)="isInstallAppliance =!isInstallAppliance">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div *ngIf="!isExistImage && isInstallAppliance">
|
||||
<div class="row" style="display: flex;">
|
||||
<div class="col-md-6">
|
||||
<h5>Please Select image</h5>
|
||||
|
||||
</div>
|
||||
<div class="col-md-4 txt-align">
|
||||
<input type="file" accept=".qcow2, .bin,.image,.qcow2,.vmdk,.img,.tmp" multiple #file class="non-visible"
|
||||
(change)="uploadImageFile($event)" />
|
||||
<button mat-raised-button color="primary" (click)="file.click()" class="file-button">Browse</button>
|
||||
</div>
|
||||
<div class="col-md-2 txt-align">
|
||||
<button mat-button (click)="dialogRef.close()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" *ngFor="let img of selectFile; let i = index">
|
||||
<mat-title> {{i+1}}. {{img?.name}} </mat-title>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div *ngIf="uploadedFile">
|
||||
<mat-progress-bar mode="indeterminate" [value]="uploadProgress" aria-valuemin="0" aria-valuemax="100">
|
||||
</mat-progress-bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="isExistImage">
|
||||
<div mat-dialog-content>
|
||||
<div *ngIf="uploadFileMessage.length > 0">
|
||||
<p class="uploaded-text">Uploaded image details</p>
|
||||
<p *ngFor="let uploadFile of uploadFileMessage; let i = index" [ngClass]="{'uploaded-error-text': uploadFile?.error?.message}">{{i+1}}. {{uploadFile?.filename ?? uploadFile?.error?.message}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-raised-button color="primary" (click)="dialogRef.close(false)">Close</button>
|
||||
</div>
|
||||
</div>
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<AddImageDialogComponent>;
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
@ -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<AddImageDialogComponent>,
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
<div *ngIf="!isDelete && !isUsedFiles">
|
||||
<h1 mat-dialog-title>Do you want delete all files ?.</h1>
|
||||
<div mat-dialog-content>
|
||||
<p>Your selected files</p>
|
||||
<p *ngFor="let file of deleteData?.deleteFilesPaths; let i = index">{{i+1}}. {{file?.filename}}</p>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-button (click)="deleteAll()">Delete</button>
|
||||
<button mat-button mat-dialog-close cdkFocusInitial>Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="isDelete && !isUsedFiles">
|
||||
<h1 align="center" mat-dialog-title>Please wait.</h1>
|
||||
|
||||
<div mat-dialog-content align="center">
|
||||
<mat-spinner color="accent"></mat-spinner>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="isDelete && isUsedFiles">
|
||||
<div mat-dialog-content>
|
||||
<div *ngIf="deleteFliesDetails.length > 0">
|
||||
<h5>Images can't be deleted because image used in one or more template.</h5>
|
||||
<p *ngFor="let message of deleteFliesDetails; let i = index" [ngClass]="{'deleted-error-text': message?.error?.message}"><span *ngIf="message !=null">{{i+1}}. {{message?.error?.message}}</span></p>
|
||||
</div>
|
||||
<div *ngIf="fileNotDeleted.length > 0">
|
||||
<h5 class="delete-text">{{fileNotDeleted.length}} Images deleted successfully.</h5>
|
||||
</div>
|
||||
</div>
|
||||
<div mat-dialog-actions align="end">
|
||||
<button mat-raised-button color="primary" (click)="dialogRef.close(false)">Close</button>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,11 @@
|
||||
.delete-text{
|
||||
color: #0ca8c7;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.deleted-error-text{
|
||||
color: #d52435;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
|
@ -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<DeleteAllImageFilesDialogComponent>;
|
||||
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();
|
||||
});
|
||||
});
|
@ -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<DeleteAllImageFilesDialogComponent>,
|
||||
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
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
36
src/app/components/image-manager/image-database-file.ts
Normal file
36
src/app/components/image-manager/image-database-file.ts
Normal file
@ -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<Image[]> = new BehaviorSubject<Image[]>([]);
|
||||
get data(): Image[] {
|
||||
return this.dataChange.value;
|
||||
}
|
||||
|
||||
public addImages(fileData: Image[]) {
|
||||
this.dataChange.next(fileData);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class imageDataSource extends DataSource<Image> {
|
||||
constructor(private serverDatabase: imageDatabase) {
|
||||
super();
|
||||
}
|
||||
|
||||
connect(): Observable<Image[]> {
|
||||
return merge(this.serverDatabase.dataChange).pipe(
|
||||
map(() => {
|
||||
return this.serverDatabase.data;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
disconnect() { }
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
<div class="content">
|
||||
<div class="default-header">
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<h1>Image Manager</h1>
|
||||
</div>
|
||||
<div class="col-md-3 btn-box">
|
||||
<button class="img-btn" mat-button
|
||||
(click)="addImageDialog()">
|
||||
<mat-icon>add</mat-icon> Add Image
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div class="default-content">
|
||||
<app-server-discovery></app-server-discovery>
|
||||
|
||||
<div class="mat-elevation-z8">
|
||||
<mat-table #table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="select">
|
||||
<mat-header-cell *matHeaderCellDef>
|
||||
<mat-checkbox (change)="$event ? selectAllImages() : null" [checked]="selection.hasValue() && isAllSelected()"
|
||||
[indeterminate]="selection.hasValue() && !isAllSelected()">
|
||||
</mat-checkbox>
|
||||
</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<mat-checkbox (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(row) : null"
|
||||
[checked]="selection.isSelected(row)">
|
||||
</mat-checkbox>
|
||||
</mat-cell>
|
||||
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="filename">
|
||||
<mat-header-cell *matHeaderCellDef> File Name </mat-header-cell>
|
||||
<mat-cell *matCellDef="let row">
|
||||
<a class="table-link">{{ row.filename }}</a>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="image_type">
|
||||
<mat-header-cell *matHeaderCellDef> Image Type </mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"> {{ row.image_type }} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="image_size">
|
||||
<mat-header-cell *matHeaderCellDef> Image Size </mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"> {{ (row.image_size/1000000).toFixed()}} MB </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="delete" >
|
||||
<mat-header-cell *matHeaderCellDef>
|
||||
<button mat-button *ngIf="(selection.hasValue() && isAllSelected()) || selection.selected.length > 1" (click)="deleteAllFiles()" aria-label="Example icon button with a delete icon">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row" >
|
||||
<button mat-button *ngIf="selection.isSelected(row)" (click)="deleteFile(row.path)" aria-label="Example icon button with a delete icon">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||
</mat-table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<ImageManagerComponent>;
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
149
src/app/components/image-manager/image-manager.component.ts
Normal file
149
src/app/components/image-manager/image-manager.component.ts
Normal file
@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -29,16 +29,16 @@
|
||||
</form>
|
||||
</mat-step>
|
||||
|
||||
<mat-step label="QEMU binary and memory" [completed]="memoryForm.get('ramMemory').value && selectedBinary">
|
||||
<mat-step label="Platform and memory" [completed]="memoryForm.get('ramMemory').value && selectedPlatform">
|
||||
<form [formGroup]="memoryForm">
|
||||
<mat-form-field class="form-field">
|
||||
<mat-select
|
||||
placeholder="Qemu binary"
|
||||
[(ngModel)]="selectedBinary"
|
||||
placeholder="Platform"
|
||||
[(ngModel)]="selectedPlatform"
|
||||
[ngModelOptions]="{ standalone: true }"
|
||||
>
|
||||
<mat-option *ngFor="let binary of qemuBinaries" [value]="binary">
|
||||
{{ binary.path }}
|
||||
<mat-option *ngFor="let platform of selectPlatform " [value]="platform">
|
||||
{{ platform }}
|
||||
</mat-option>
|
||||
</mat-select> </mat-form-field
|
||||
><br />
|
||||
|
@ -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 {
|
||||
|
@ -192,7 +192,6 @@
|
||||
<button mat-button class="configButton" (click)="setCustomAdaptersConfiguratorState(true)">
|
||||
Configure custom adapters</button
|
||||
><br />
|
||||
<mat-checkbox [(ngModel)]="qemuTemplate.legacy_networking"> Use the legacy networking mode </mat-checkbox>
|
||||
</mat-expansion-panel>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
|
@ -0,0 +1,4 @@
|
||||
<button mat-menu-item (click)="editStyle()">
|
||||
<mat-icon>style</mat-icon>
|
||||
<span>Edit style</span>
|
||||
</button>
|
@ -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;
|
||||
}
|
||||
}
|
@ -180,6 +180,18 @@
|
||||
[server]="server"
|
||||
[link]="links[0]"
|
||||
></app-reset-link-action>
|
||||
<app-edit-link-style-action
|
||||
*ngIf="
|
||||
!projectService.isReadOnly(project) &&
|
||||
drawings.length === 0 &&
|
||||
nodes.length === 0 &&
|
||||
links.length === 1 &&
|
||||
linkNodes.length === 0
|
||||
"
|
||||
[server]="server"
|
||||
[project]="project"
|
||||
[link]="links[0]"
|
||||
></app-edit-link-style-action>
|
||||
<app-lock-action
|
||||
*ngIf="!projectService.isReadOnly(project) && (drawings.length > 0 || nodes.length > 0)"
|
||||
[server]="server"
|
||||
|
@ -0,0 +1,33 @@
|
||||
<h1 mat-dialog-title>Style editor</h1>
|
||||
|
||||
<div class="modal-form-container">
|
||||
<form [formGroup]="formGroup">
|
||||
<mat-form-field class="form-field">
|
||||
<input
|
||||
matInput
|
||||
placeholder="Color"
|
||||
formControlName="color"
|
||||
type="color"
|
||||
/>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<input
|
||||
matInput
|
||||
formControlName="width"
|
||||
placeholder="Width"
|
||||
type="number" />
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field class="form-field">
|
||||
<mat-select placeholder="Type" formControlName="type">
|
||||
<mat-option *ngFor="let type of borderTypes" [value]="type"> {{ type }} </mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div mat-dialog-actions>
|
||||
<button mat-button (click)="onNoClick()" color="accent">Cancel</button>
|
||||
<button mat-button (click)="onYesClick()" tabindex="2" mat-raised-button color="primary">Apply</button>
|
||||
</div>
|
@ -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%;
|
||||
}
|
||||
|
@ -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<LinkStyleEditorDialogComponent>,
|
||||
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`);
|
||||
}
|
||||
}
|
||||
}
|
@ -129,25 +129,8 @@
|
||||
<mat-step *ngIf="applianceToInstall">
|
||||
<ng-template matStepLabel>{{ secondActionTitle }}</ng-template>
|
||||
|
||||
<mat-card [hidden]="!(!isLinuxPlatform && !applianceToInstall.dynamips)">
|
||||
Please configure GNS3 VM to install selected appliance
|
||||
</mat-card>
|
||||
|
||||
<mat-card [hidden]="!(isLinuxPlatform || applianceToInstall.dynamips)">
|
||||
<mat-card [hidden]="!(!isLinuxPlatform || applianceToInstall.dynamips)">
|
||||
<div *ngIf="applianceToInstall.qemu">
|
||||
<div>
|
||||
Server type<br />
|
||||
<mat-radio-group class="radio-group">
|
||||
<mat-radio-button
|
||||
[disabled]="!isLinuxPlatform"
|
||||
[checked]="isLocalComputerChosen"
|
||||
class="radio-button"
|
||||
value="1"
|
||||
(click)="setServerType('local')"
|
||||
>Install the appliance locally</mat-radio-button
|
||||
>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
<div>
|
||||
Qemu binary<br />
|
||||
<mat-select
|
||||
@ -321,37 +304,13 @@
|
||||
</div>
|
||||
|
||||
<div *ngIf="applianceToInstall.docker">
|
||||
<div>
|
||||
Server type<br />
|
||||
<mat-radio-group class="radio-group">
|
||||
<mat-radio-button
|
||||
[disabled]="!isLinuxPlatform"
|
||||
[checked]="isLocalComputerChosen"
|
||||
class="radio-button"
|
||||
value="1"
|
||||
(click)="setServerType('local')"
|
||||
>Install the appliance locally</mat-radio-button
|
||||
>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
|
||||
<button mat-raised-button color="primary" (click)="createDockerTemplate()" class="create-button">
|
||||
Create docker template
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *ngIf="applianceToInstall.dynamips">
|
||||
<div>
|
||||
Server type<br />
|
||||
<mat-radio-group class="radio-group">
|
||||
<mat-radio-button
|
||||
[checked]="isLocalComputerChosen"
|
||||
class="radio-button"
|
||||
value="1"
|
||||
(click)="setServerType('local')"
|
||||
>Install the appliance locally</mat-radio-button
|
||||
>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
<div>
|
||||
Install required files
|
||||
<button
|
||||
@ -389,19 +348,6 @@
|
||||
</div>
|
||||
|
||||
<div *ngIf="applianceToInstall.iou">
|
||||
<div>
|
||||
Server type<br />
|
||||
<mat-radio-group class="radio-group">
|
||||
<mat-radio-button
|
||||
[disabled]="!isLinuxPlatform"
|
||||
[checked]="isLocalComputerChosen"
|
||||
class="radio-button"
|
||||
value="1"
|
||||
(click)="setServerType('local')"
|
||||
>Install the appliance locally</mat-radio-button
|
||||
>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
<div>
|
||||
Install required files
|
||||
<button
|
||||
|
@ -42,7 +42,7 @@ import { TemplateNameDialogComponent } from './template-name-dialog/template-nam
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class NewTemplateDialogComponent implements OnInit {
|
||||
export class NewTemplateDialogComponent implements OnInit {
|
||||
@Input() server: Server;
|
||||
@Input() project: Project;
|
||||
|
||||
@ -96,6 +96,20 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.applianceService.getAppliances(this.server).subscribe((appliances) => {
|
||||
this.appliances = appliances;
|
||||
this.appliances.forEach((appliance) => {
|
||||
if (appliance.docker) appliance.emulator = 'Docker';
|
||||
if (appliance.dynamips) appliance.emulator = 'Dynamips';
|
||||
if (appliance.iou) appliance.emulator = 'Iou';
|
||||
if (appliance.qemu) appliance.emulator = 'Qemu';
|
||||
});
|
||||
this.allAppliances = appliances;
|
||||
this.dataSource = new MatTableDataSource(this.allAppliances);
|
||||
this.dataSource.paginator = this.paginator;
|
||||
});
|
||||
|
||||
if(!this.server.authToken){
|
||||
this.templateService.list(this.server).subscribe((templates) => {
|
||||
this.templates = templates;
|
||||
});
|
||||
@ -106,6 +120,10 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
});
|
||||
});
|
||||
|
||||
this.qemuService.getBinaries(this.server).subscribe((binaries) => {
|
||||
this.qemuBinaries = binaries;
|
||||
});
|
||||
|
||||
this.qemuService.getImages(this.server).subscribe((qemuImages) => {
|
||||
this.qemuImages = qemuImages;
|
||||
});
|
||||
@ -131,10 +149,6 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
});
|
||||
|
||||
this.qemuService.getBinaries(this.server).subscribe((binaries) => {
|
||||
this.qemuBinaries = binaries;
|
||||
});
|
||||
|
||||
this.uploader = new FileUploader({});
|
||||
this.uploader.onAfterAddingFile = (file) => {
|
||||
file.withCredentials = false;
|
||||
@ -182,6 +196,8 @@ export class NewTemplateDialogComponent implements OnInit {
|
||||
this.uploaderImage.clearQueue();
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
updateAppliances() {
|
||||
this.progressService.activate();
|
||||
|
@ -143,9 +143,7 @@
|
||||
</div>
|
||||
</mat-tab>
|
||||
<mat-tab label="Network">
|
||||
<br /><mat-checkbox [(ngModel)]="node.properties.legacy_networking">
|
||||
Use the legacy networking mode
|
||||
</mat-checkbox>
|
||||
<br />
|
||||
<br /><mat-checkbox [(ngModel)]="node.properties.replicate_network_connection_state">
|
||||
Replicate network connection state
|
||||
</mat-checkbox>
|
||||
|
@ -103,6 +103,10 @@
|
||||
<mat-icon>settings</mat-icon>
|
||||
<span>Go to settings</span>
|
||||
</button>
|
||||
<button mat-menu-item [routerLink]="['/server', server.id, 'image-manager']">
|
||||
<mat-icon>collections</mat-icon>
|
||||
<span>Image manager</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="addNewTemplate()">
|
||||
<mat-icon>control_point</mat-icon>
|
||||
<span>New template</span>
|
||||
|
@ -268,6 +268,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.projectMapSubscription.add(
|
||||
this.linksDataSource.changes.subscribe((links: Link[]) => {
|
||||
console.log('from project map component');
|
||||
this.links = links;
|
||||
this.mapChangeDetectorRef.detectChanges();
|
||||
})
|
||||
|
14
src/app/models/images.ts
Normal file
14
src/app/models/images.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export class Image {
|
||||
filename: string;
|
||||
path: string;
|
||||
image_type: string;
|
||||
image_size: number;
|
||||
checksum: string;
|
||||
checksum_algorithm: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export class ImageData {
|
||||
|
||||
}
|
5
src/app/models/link-style.ts
Normal file
5
src/app/models/link-style.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export class LinkStyle {
|
||||
color: string;
|
||||
width: number;
|
||||
type: number;
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
import { Node } from '../cartography/models/node';
|
||||
import { Filter } from './filter';
|
||||
import { LinkNode } from './link-node';
|
||||
import { LinkStyle } from './link-style';
|
||||
|
||||
export class Link {
|
||||
capture_file_name: string;
|
||||
@ -12,6 +13,7 @@ export class Link {
|
||||
nodes: LinkNode[];
|
||||
project_id: string;
|
||||
suspend: boolean;
|
||||
link_style?: LinkStyle;
|
||||
|
||||
distance: number; // this is not from server
|
||||
length: number; // this is not from server
|
||||
|
@ -27,7 +27,6 @@ export class QemuTemplate {
|
||||
initrd: string;
|
||||
kernel_command_line: string;
|
||||
kernel_image: string;
|
||||
legacy_networking: boolean;
|
||||
linked_clone: boolean;
|
||||
mac_address: string;
|
||||
name: string;
|
||||
|
60
src/app/services/image-manager.service.spec.ts
Normal file
60
src/app/services/image-manager.service.spec.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
|
||||
import { inject, TestBed } from '@angular/core/testing';
|
||||
import { AppTestingModule } from 'app/testing/app-testing/app-testing.module';
|
||||
import { Server } from '../models/server';
|
||||
import { HttpServer } from './http-server.service';
|
||||
import { getTestServer } from './testing';
|
||||
|
||||
import { ImageManagerService } from './image-manager.service';
|
||||
import { Image } from "../models/images";
|
||||
|
||||
describe('ImageManagerService', () => {
|
||||
let httpClient: HttpClient;
|
||||
let httpTestingController: HttpTestingController;
|
||||
let httpServer: HttpServer;
|
||||
let server: Server;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [HttpClientTestingModule, AppTestingModule],
|
||||
providers: [HttpServer, ImageManagerService],
|
||||
});
|
||||
|
||||
httpClient = TestBed.get(HttpClient);
|
||||
httpTestingController = TestBed.get(HttpTestingController);
|
||||
httpServer = TestBed.get(HttpServer);
|
||||
server = getTestServer();
|
||||
// service = TestBed.inject(ImageManagerService);
|
||||
});
|
||||
afterEach(() => {
|
||||
httpTestingController.verify();
|
||||
});
|
||||
|
||||
|
||||
it('should be get Images', inject([ImageManagerService], (service: ImageManagerService) => {
|
||||
service.getImages(server).subscribe();
|
||||
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v3/images');
|
||||
expect(req.request.method).toEqual('GET');
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should add image', inject([ImageManagerService], (service: ImageManagerService) => {
|
||||
let install_appliance = true
|
||||
const image: Image = {
|
||||
filename: '',
|
||||
path: '',
|
||||
image_type: '',
|
||||
image_size: 0,
|
||||
checksum: '',
|
||||
checksum_algorithm: '',
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
};
|
||||
|
||||
service.uploadedImage(server, install_appliance, image.filename, image).subscribe();
|
||||
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v3/images/upload/?install_appliances=true');
|
||||
expect(req.request.method).toEqual('POST');
|
||||
expect(req.request.body).toEqual(image);
|
||||
}));
|
||||
});
|
24
src/app/services/image-manager.service.ts
Normal file
24
src/app/services/image-manager.service.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Server } from '../models/server';
|
||||
import { HttpServer } from './http-server.service';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Image } from "../models/images";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ImageManagerService {
|
||||
|
||||
constructor(private httpServer: HttpServer) { }
|
||||
|
||||
getImages(server: Server) {
|
||||
return this.httpServer.get<Image[]>(server, '/images') as Observable<Image[]>;
|
||||
}
|
||||
|
||||
uploadedImage(server:Server, install_appliance, image_path, flie){
|
||||
return this.httpServer.post<Image[]>(server, `/images/upload/${image_path}?install_appliances=${install_appliance}`,flie) as Observable<Image[]>;
|
||||
}
|
||||
deleteFile(server:Server, image_path){
|
||||
return this.httpServer.delete<Image[]>(server, `/images/${image_path}`) as Observable<Image[]>;
|
||||
}
|
||||
}
|
@ -69,6 +69,10 @@ export class LinkService {
|
||||
return this.httpServer.put<Link>(server, `/projects/${link.project_id}/links/${link.link_id}`, link);
|
||||
}
|
||||
|
||||
updateLinkStyle(server: Server, link: Link) {
|
||||
return this.httpServer.put<Link>(server, `/projects/${link.project_id}/links/${link.link_id}`, link);
|
||||
}
|
||||
|
||||
getAvailableFilters(server: Server, link: Link) {
|
||||
return this.httpServer.get<FilterDescription[]>(
|
||||
server,
|
||||
|
@ -2,6 +2,10 @@ import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class QemuConfigurationService {
|
||||
getPlatform() {
|
||||
return ['x86_64', 'aarch64', 'alpha', 'arm', 'cris', 'i386', 'lm32', 'm68k', 'microblaze', 'microblazeel', 'mips', 'mips64', 'mips64el', 'mipsel', 'moxie', 'or32', 'ppc', 'ppc64', 'ppcemb', 's390x', 'sh4', 'sh4eb', 'sparc', 'sparc64', 'tricore', 'unicore32', 'xtensa', 'xtensaeb'];
|
||||
}
|
||||
|
||||
getConsoleTypes() {
|
||||
return ['telnet', 'vnc', 'spice', 'spice+agent', 'none'];
|
||||
}
|
||||
|
@ -62,7 +62,6 @@ describe('QemuService', () => {
|
||||
initrd: '',
|
||||
kernel_command_line: '',
|
||||
kernel_image: '',
|
||||
legacy_networking: false,
|
||||
linked_clone: true,
|
||||
mac_address: '',
|
||||
name: '',
|
||||
@ -115,7 +114,6 @@ describe('QemuService', () => {
|
||||
initrd: '',
|
||||
kernel_command_line: '',
|
||||
kernel_image: '',
|
||||
legacy_networking: false,
|
||||
linked_clone: true,
|
||||
mac_address: '',
|
||||
name: '',
|
||||
|
@ -20,7 +20,7 @@ export class QemuService {
|
||||
}
|
||||
|
||||
getImagePath(server: Server, filename: string): string {
|
||||
return `${server.protocol}//${server.host}:${server.port}/v3/compute/qemu/images/${filename}`;
|
||||
return `${server.protocol}//${server.host}:${server.port}/v3/images/upload/${filename}`;
|
||||
}
|
||||
|
||||
getBinaries(server: Server): Observable<QemuBinary[]> {
|
||||
@ -28,11 +28,11 @@ export class QemuService {
|
||||
}
|
||||
|
||||
getImages(server: Server): Observable<any> {
|
||||
return this.httpServer.get<QemuImage[]>(server, '/compute/qemu/images') as Observable<QemuImage[]>;
|
||||
return this.httpServer.get<QemuImage[]>(server, '/images') as Observable<QemuImage[]>;
|
||||
}
|
||||
|
||||
addImage(server: Server, qemuImg: QemuImg): Observable<QemuImg> {
|
||||
return this.httpServer.post<QemuImg>(server, '/compute/qemu/img', qemuImg) as Observable<QemuImg>;
|
||||
return this.httpServer.post<QemuImg>(server, '/images/upload', qemuImg) as Observable<QemuImg>;
|
||||
}
|
||||
|
||||
addTemplate(server: Server, qemuTemplate: QemuTemplate): Observable<QemuTemplate> {
|
||||
|
@ -41,7 +41,6 @@ export class TemplateMocksService {
|
||||
initrd: '',
|
||||
kernel_command_line: '',
|
||||
kernel_image: '',
|
||||
legacy_networking: false,
|
||||
linked_clone: true,
|
||||
mac_address: '',
|
||||
name: '',
|
||||
|
Loading…
x
Reference in New Issue
Block a user