├── .gitignore ├── backend ├── config.ini ├── error.py ├── inventory.py ├── socketio.py ├── __init__.py ├── devices.py └── data.py ├── frontend ├── plugins │ ├── plugins.component.scss │ ├── plugins.component.html │ └── plugins.component.ts ├── monitoring │ ├── monitoring.component.scss │ ├── monitoring.component.ts │ └── monitoring.component.html ├── common │ ├── loading │ │ ├── loading.component.html │ │ ├── loading.component.ts │ │ └── loading.component.scss │ └── pipes.ts ├── inventory │ ├── inventory.component.html │ ├── schema.ts │ ├── device.ts │ ├── devices.service.ts │ ├── schemas.component.html │ ├── inventory.component.ts │ ├── inventory.component.scss │ ├── schemas.component.ts │ └── devices.component.html ├── package.json ├── netopeer.component.html ├── dashboard.component.ts ├── config │ ├── config.component.scss │ ├── session.ts │ ├── tree-create.html │ ├── tree-edit.html │ ├── tree.component.html │ ├── tree.component.scss │ ├── config.component.html │ ├── tree-indent.html │ └── tree-node.html ├── netopeer.scss ├── dashboard.component.html ├── yang │ ├── yang.feature.html │ ├── yang.typedef.html │ ├── yang.restriction.html │ ├── yang.identity.html │ ├── yang.component.scss │ ├── yang.component.html │ └── yang.type.html ├── netopeer.component.ts ├── _netopeer-common.scss └── netopeer.module.ts ├── frontend-assets ├── logo.png └── icons │ ├── show_children.svg │ ├── tree_empty.svg │ ├── show_children_active.svg │ ├── add.svg │ ├── add_active.svg │ ├── close.svg │ ├── close_active.svg │ ├── show_all.svg │ ├── collapse.svg │ ├── collapse_active.svg │ ├── show_all_active.svg │ ├── tree_root.svg │ ├── tree_last_branch.svg │ ├── tree_cont.svg │ ├── leaf.svg │ ├── menu.svg │ ├── menu_active.svg │ ├── tree_branch.svg │ ├── info.svg │ ├── info_active.svg │ ├── key.svg │ ├── container.svg │ ├── leaflist.svg │ ├── show.svg │ ├── show_active.svg │ ├── edit.svg │ ├── edit_active.svg │ ├── back.svg │ └── module.svg ├── vagrant ├── Ubuntu-release │ ├── netopeer-config.ini │ ├── run.sh │ ├── setvenv.sh │ ├── ncgui.service │ ├── ncgui.conf │ ├── lgui-config.ini │ └── Vagrantfile ├── README.md ├── OpenSUSE │ └── Vagrantfile └── Ubuntu │ └── Vagrantfile ├── docs └── screenshots │ ├── yang.png │ ├── devices.png │ └── configuration.png ├── app.config.json ├── config.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /backend/config.ini: -------------------------------------------------------------------------------- 1 | [netopeer] 2 | usersdata_path=./ -------------------------------------------------------------------------------- /frontend/plugins/plugins.component.scss: -------------------------------------------------------------------------------- 1 | @import '../netopeer-common'; -------------------------------------------------------------------------------- /frontend/monitoring/monitoring.component.scss: -------------------------------------------------------------------------------- 1 | @import '../netopeer-common'; -------------------------------------------------------------------------------- /frontend-assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CESNET/Netopeer2GUI/HEAD/frontend-assets/logo.png -------------------------------------------------------------------------------- /vagrant/Ubuntu-release/netopeer-config.ini: -------------------------------------------------------------------------------- 1 | [netopeer] 2 | usersdata_path=/var/www/html/ncgui/data 3 | -------------------------------------------------------------------------------- /docs/screenshots/yang.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CESNET/Netopeer2GUI/HEAD/docs/screenshots/yang.png -------------------------------------------------------------------------------- /docs/screenshots/devices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CESNET/Netopeer2GUI/HEAD/docs/screenshots/devices.png -------------------------------------------------------------------------------- /docs/screenshots/configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CESNET/Netopeer2GUI/HEAD/docs/screenshots/configuration.png -------------------------------------------------------------------------------- /backend/error.py: -------------------------------------------------------------------------------- 1 | #!/bin/python3 2 | 3 | from liberouterapi.error import ApiException 4 | 5 | class NetopeerException(ApiException): 6 | status_code = 500 7 | -------------------------------------------------------------------------------- /vagrant/Ubuntu-release/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source /var/www/html/ncgui/backend/venv/bin/activate 3 | python3 /var/www/html/ncgui/backend/ & 4 | deactivate 5 | 6 | -------------------------------------------------------------------------------- /vagrant/Ubuntu-release/setvenv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | virtualenv venv --system-site-packages -p python3 3 | source venv/bin/activate 4 | pip3 install --upgrade pip 5 | pip3 install -r requirements.txt 6 | deactivate 7 | 8 | -------------------------------------------------------------------------------- /frontend/common/loading/loading.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /frontend/inventory/inventory.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /vagrant/Ubuntu-release/ncgui.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Netopeer2GUI backend 3 | 4 | [Service] 5 | Type=oneshot 6 | ExecStart=/var/www/html/ncgui/backend/run.sh 7 | RemainAfterExit=true 8 | StandardOutput=syslog 9 | StandardError=syslog 10 | 11 | [Install] 12 | WantedBy=multi-user.target 13 | -------------------------------------------------------------------------------- /frontend/inventory/schema.ts: -------------------------------------------------------------------------------- 1 | export class Schema { 2 | constructor ( 3 | public key: string, 4 | public name: string = '', 5 | public revision: string = '', 6 | public type: string = '', 7 | public path: string = '', 8 | public data: any = null, 9 | public sections: string[] = [] 10 | ) {} 11 | } 12 | -------------------------------------------------------------------------------- /frontend/monitoring/monitoring.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector : 'netopeer-config', 5 | templateUrl : './monitoring.component.html', 6 | styleUrls : ['./monitoring.component.scss'] 7 | }) 8 | 9 | export class MonitoringComponent { 10 | title = 'Monitoring'; 11 | } 12 | -------------------------------------------------------------------------------- /app.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "logo" : "assets/netopeer/logo.png", 3 | "name" : "Netopeer", 4 | "colorTheme" : { 5 | "colorMain" : "#354b68", 6 | "colorHighlight" : "#3d5676", 7 | "colorSelected" : "#3d5676", 8 | "colorSelected2" : "#6888b1" 9 | }, 10 | "api" : { 11 | "url" : "/libapi", 12 | "host" : null, 13 | "port" : null, 14 | "proto" : null 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "npm" : "frontend/package.json" 4 | }, 5 | "module": { 6 | "name" : "netopeer", 7 | "file" : "netopeer.module.ts", 8 | "class" : "NetopeerModule", 9 | "hooks" : "NetopeerModuleHooks", 10 | "frontend" : "frontend", 11 | "backend" : "backend", 12 | "assets" : "frontend-assets" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /frontend/monitoring/monitoring.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Monitoring (TBD)

4 |

Work with NETCONF notifications from the connected devices.

5 | 10 | 11 |
-------------------------------------------------------------------------------- /frontend/common/loading/loading.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ViewEncapsulation, Input} from '@angular/core'; 2 | 3 | @Component({ 4 | selector : 'netopeer-loading', 5 | templateUrl : './loading.component.html', 6 | styleUrls : ['./loading.component.scss'], 7 | encapsulation: ViewEncapsulation.None 8 | }) 9 | 10 | export class LoadingComponent { 11 | @Input() spinner = false; 12 | @Input() diameter = 50; 13 | @Input() strokeWidth = 7; 14 | } 15 | -------------------------------------------------------------------------------- /frontend/plugins/plugins.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |

Plugins (TBD)

4 |

Framework for schema(s) specific applications - simplified/more user friendly configuration approach than the generic configuration tree in the configuration tab, for example:

5 | 10 | {{text}} 11 |
-------------------------------------------------------------------------------- /frontend/inventory/device.ts: -------------------------------------------------------------------------------- 1 | export class Device { 2 | constructor ( 3 | public id: number, 4 | public name:string = '', 5 | public hostname: string = '', 6 | public port: number = 830, 7 | public autoconnect: boolean = false, 8 | public username: string = '', 9 | public password: string = '', 10 | public fingerprint: string = '', 11 | ) {} 12 | } 13 | /* 14 | export class Device { 15 | id: number; 16 | hostname: string; 17 | port: number = 830; 18 | username: string; 19 | password: string; 20 | } 21 | */ 22 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Netopeer2GUI", 3 | "version": "0.1.0", 4 | "description": "NETCONF management center", 5 | "main": "index.html", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/CESNET/Netopeer2GUI.git" 9 | }, 10 | "author": "", 11 | "license": "BSD", 12 | "bugs": { 13 | "url": "https://github.com/CESNET/Netopeer2GUI/issues" 14 | }, 15 | "homepage": "https://github.com/CESNET/Netopeer2GUI", 16 | "dependencies" : { 17 | "@angular/material": "^6.0.0", 18 | "@angular/cdk": "^6.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /frontend/plugins/plugins.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | @Component({ 4 | selector : 'netopeer-plugins', 5 | templateUrl : './plugins.component.html', 6 | styleUrls : ['./plugins.component.scss'] 7 | }) 8 | 9 | export class PluginsComponent implements OnInit { 10 | title = 'Plugins'; 11 | text = "test... ignore"; 12 | 13 | sleep(ms) { 14 | return new Promise(resolve => setTimeout(resolve, ms)); 15 | } 16 | 17 | async ngOnInit() { 18 | await this.sleep(2000); 19 | this.text = "still testing... still ignore"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /frontend/netopeer.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Netopeer {{componentTitle}}

3 | 4 | 8 | 9 |
10 |
11 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /backend/inventory.py: -------------------------------------------------------------------------------- 1 | """ 2 | Manipulation with the YANG schemas. 3 | File: schemas.py 4 | Author: Radek Krejci 5 | """ 6 | 7 | import os 8 | import errno 9 | 10 | from liberouterapi import config 11 | 12 | from .error import NetopeerException 13 | 14 | INVENTORY = config['netopeer'].get('usersdata_path', './') 15 | 16 | def inventory_check(path): 17 | try: 18 | os.makedirs(path, mode=0o750) 19 | except OSError as e: 20 | if e.errno == errno.EEXIST and os.path.isdir(path): 21 | pass 22 | elif e.errno == errno.EEXIST: 23 | raise NetopeerException('User\'s inventory (' + path + ') already exists and it\'s not a directory.') 24 | else: 25 | raise NetopeerException('Unable to use inventory path ' + path +' (' + str(e) + ').') 26 | 27 | -------------------------------------------------------------------------------- /backend/socketio.py: -------------------------------------------------------------------------------- 1 | """ 2 | Socket IO helper functions 3 | File: socketio.py 4 | Author: Radek Krejci 5 | """ 6 | 7 | from eventlet import event 8 | 9 | from liberouterapi import socketio 10 | 11 | sio_data = {} 12 | 13 | 14 | def sio_send(data): 15 | try: 16 | e = sio_data[data['id']] 17 | e.send(data) 18 | except KeyError: 19 | pass 20 | 21 | 22 | def sio_emit(name, params): 23 | socketio.emit(name, params, callback = sio_send) 24 | 25 | 26 | def sio_wait(id): 27 | e = sio_data[id] = event.Event() 28 | return e.wait() 29 | 30 | 31 | def sio_clean(id): 32 | sio_data.pop(id, None) 33 | 34 | 35 | @socketio.on('device_auth_password') 36 | @socketio.on('hostcheck_result') 37 | @socketio.on('getschema_result') 38 | def process_answer(data): 39 | sio_send(data) 40 | -------------------------------------------------------------------------------- /frontend/common/loading/loading.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../netopeer-common'; 2 | 3 | .mat-spinner circle { 4 | stroke:$colorMain; 5 | } 6 | 7 | .mat-progress-bar-background { 8 | background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg%20version%3D%271.1%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20xmlns%3Axlink%3D%27http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%27%20x%3D%270px%27%20y%3D%270px%27%20enable-background%3D%27new%200%200%205%202%27%20xml%3Aspace%3D%27preserve%27%20viewBox%3D%270%200%205%202%27%20preserveAspectRatio%3D%27none%20slice%27%3E%3Ccircle%20cx%3D%271%27%20cy%3D%271%27%20r%3D%271%27%20fill%3D%27%23B2EBF2%27%2F%3E%3C%2Fsvg%3E") 9 | } 10 | .mat-progress-bar-buffer{ 11 | background-color:$lightGrey; 12 | } 13 | .mat-progress-bar-fill::after{ 14 | background-color:$colorMain; 15 | } 16 | -------------------------------------------------------------------------------- /frontend/dashboard.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {Router} from '@angular/router'; 3 | 4 | import {SessionsService} from './config/sessions.service'; 5 | 6 | @Component({ 7 | selector : 'netopeer-dashboard', 8 | templateUrl : './dashboard.component.html', 9 | styleUrls : ['./netopeer.scss', 'inventory/inventory.component.scss'] 10 | }) 11 | 12 | export class DashboardComponent implements OnInit { 13 | 14 | constructor(public sessionsService: SessionsService, 15 | private router: Router) {} 16 | 17 | gotoConfig(session) { 18 | this.sessionsService.changeActiveSession(session.key); 19 | this.router.navigateByUrl('/netopeer/config'); 20 | } 21 | 22 | ngOnInit(): void { 23 | this.sessionsService.checkSessions(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /vagrant/Ubuntu-release/ncgui.conf: -------------------------------------------------------------------------------- 1 | 2 | ServerAdmin webmaster@localhost 3 | DocumentRoot /var/www/html/ncgui 4 | 5 | ErrorLog ${APACHE_LOG_DIR}/error.log 6 | CustomLog ${APACHE_LOG_DIR}/access.log combined 7 | 8 | RewriteEngine on 9 | RewriteRule ^/libapi(.*) http://[::1]:5555$1 [P] 10 | 11 | RewriteCond %{QUERY_STRING} transport=polling [NC] 12 | RewriteRule /(.*) http://[::1]:5555/$1 [P] 13 | 14 | RewriteCond %{HTTP:Upgrade} websocket [NC] 15 | RewriteRule /(.*) ws://[::1]:5555/$1 [P] 16 | 17 | 18 | AllowOverride All 19 | Options -Indexes 20 | 21 | RewriteCond %{REQUEST_FILENAME} -f [OR] 22 | RewriteCond %{REQUEST_FILENAME} -l [OR] 23 | RewriteCond %{REQUEST_FILENAME} -d 24 | RewriteRule ^.*$ - [L] 25 | RewriteRule ^.* /index.html [L] 26 | 27 | 28 | -------------------------------------------------------------------------------- /frontend/config/config.component.scss: -------------------------------------------------------------------------------- 1 | @import '../netopeer-common'; 2 | @import '../inventory/inventory.component'; 3 | @import './tree.component'; 4 | 5 | #config-data { 6 | cursor: default; 7 | width: 100%; 8 | } 9 | 10 | .item_action_expand, 11 | .item_action_collapse { 12 | height: 1em; 13 | } 14 | 15 | .item_action_collapse:hover { 16 | color: $red; 17 | } 18 | 19 | .item_action_expand:hover { 20 | color: $green; 21 | } 22 | 23 | .modifications-status { 24 | margin-bottom: 2em; 25 | margin-right: 2em; 26 | padding: 0.5em 1em; 27 | 28 | >div { 29 | position: fixed; 30 | bottom: 2em; 31 | right: 2em; 32 | background-color: $colorChanged; 33 | border: 2px solid $colorChangedBorder; 34 | padding: 0 1em; 35 | div { 36 | padding: 0.5em 0; 37 | } 38 | } 39 | } 40 | 41 | .loading { 42 | text-align: center; 43 | margin: auto; 44 | width: 10em; 45 | div { 46 | margin: auto; 47 | width: 50px; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /frontend/netopeer.scss: -------------------------------------------------------------------------------- 1 | @import 'netopeer-common'; 2 | 3 | #netopeer-header { 4 | position: fixed; 5 | width: 100%; 6 | margin: -0.5em -1em 0em -1em; 7 | padding-top: 1em; 8 | background-color: $colorMain; 9 | color: $colorTextInverse; 10 | display: block; 11 | } 12 | 13 | #netopeer-header h1 { 14 | margin-left: 1em; 15 | color: $colorTextInverse; 16 | } 17 | 18 | #netopeer-component { 19 | margin: 0em -1em 0em -1em; 20 | } 21 | 22 | #mainnav { 23 | with: 100%; 24 | padding-left: 1em; 25 | } 26 | 27 | #mainnav a:visited, 28 | #mainnav a:link { 29 | color: inherit; 30 | } 31 | 32 | #mainnav a:hover { 33 | border-top-color: $colorHighlight; 34 | background-color: $colorHighlight; 35 | } 36 | 37 | #mainnav a.active { 38 | border-top-color: $colorSelected2; 39 | background-color: $colorSelected; 40 | } 41 | #mainnav a.active:hover { 42 | cursor: default; 43 | } 44 | 45 | #mainnav a { 46 | text-decoration: none; 47 | display: inline-block; 48 | padding: 0.5em 1em 0.5em 0.5em; 49 | color: $colorTextInverse; 50 | border-top: 0.2em solid $colorMain; 51 | } 52 | -------------------------------------------------------------------------------- /frontend/dashboard.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 17 | 18 |
 Currently connected devicesautoconnect
10 | x 13 | {{session.device.name}}
19 |
20 | -------------------------------------------------------------------------------- /frontend/config/session.ts: -------------------------------------------------------------------------------- 1 | import { Device } from '../inventory/device'; 2 | 3 | import { SessionsService } from './sessions.service'; 4 | 5 | export enum NodeType { 6 | container = 1, 7 | leaf = 4, 8 | leaflist = 8, 9 | list = 16 10 | } 11 | 12 | export class Session { 13 | constructor ( 14 | public key: string, 15 | public device: Device, 16 | public loading = false, 17 | public data: Node = null, 18 | public treeFilters = [], 19 | public modifications = null, 20 | public cpblts: string = "", 21 | public dataPresence: string = 'none', 22 | public statusVisibility: boolean = true, 23 | public cpbltsVisibility: boolean = false, 24 | ) {} 25 | } 26 | 27 | export class NodeSchema { 28 | /* 29 | * type: NodeType; 30 | * path: string; 31 | */ 32 | } 33 | 34 | export class Node { 35 | /* 36 | * path: string; 37 | * info: NodeSchema; 38 | * 39 | * === container === 40 | * children: Node[] 41 | * newChildren: Node[] 42 | * 43 | * === leaf === 44 | * value: string; 45 | * 46 | * === leaf-list === 47 | * value: string; 48 | * 49 | * === list === 50 | * children: Node[] 51 | * newChildren: Node[] 52 | */ 53 | } 54 | -------------------------------------------------------------------------------- /frontend/yang/yang.feature.html: -------------------------------------------------------------------------------- 1 |
2 |

Feature {{name}}

3 |
4 |
5 | 6 |
7 |
8 | 9 |
10 |
11 | status{{data.status.value}} 12 |
13 |
14 | description 15 |
16 |
17 | reference 18 |
19 |
20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /frontend/config/tree-create.html: -------------------------------------------------------------------------------- 1 | 2 |
5 | 6 | 7 | 13 | 14 | 15 | 16 |
17 | xThere is no element to create at {{node['path']}}. 18 |
19 |
20 |
-------------------------------------------------------------------------------- /frontend/inventory/devices.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | import { Observable } from 'rxjs/Observable'; 4 | import { catchError } from 'rxjs/operators'; 5 | 6 | import { Device } from './device'; 7 | 8 | @Injectable() 9 | export class DevicesService { 10 | constructor(private http: HttpClient) {} 11 | 12 | getDevices(): Observable { 13 | return this.http.get('/netopeer/inventory/devices/list') 14 | .pipe( 15 | catchError(err => Observable.throw(err)) 16 | ); 17 | } 18 | 19 | addDevice(device: Device) { 20 | // let options = new HttpOptions({ body: JSON.stringify(device) }); 21 | return this.http.post('/netopeer/inventory/devices', device) 22 | .pipe( 23 | catchError(err => Observable.throw(err)) 24 | ); 25 | } 26 | 27 | rmDevice(device_id: number) { 28 | // We need to use generic HTTP request, because HttpClient does not support body in DELETE requests. 29 | return this.http.request('DELETE', '/netopeer/inventory/devices', { body: JSON.stringify({'id':device_id}) }) 30 | .pipe( 31 | catchError(err => Observable.throw(err)) 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /frontend/yang/yang.typedef.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

Typedef {{name}}

4 | 5 | 6 | 7 |
8 |
9 | units{{data.units.name}} 10 |
11 |
12 | default{{data.default.value}} 13 |
14 |
15 | status{{data.status.value}} 16 |
17 |
18 | description 19 |
20 |
21 | reference 22 |
23 |
24 | 25 |
26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /frontend/yang/yang.restriction.html: -------------------------------------------------------------------------------- 1 |
2 | {{name}} 3 | 4 |
5 |
6 | {{name}} 7 | {{data.value}} 8 |
9 |
10 |
11 | modifier 12 | {{data.modifier.value}} 13 |
14 |
15 | error-message 16 | {{data['error-message'].value}} 17 |
18 |
19 | error-app-tag 20 | {{data['error-app-tag'].value}} 21 |
22 |
23 | description 24 |
{{data.description.text}}
25 |
26 |
27 | reference 28 |
{{data.reference.text}}
29 |
30 |
-------------------------------------------------------------------------------- /frontend/yang/yang.identity.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |

Identity {{name}}

4 |
5 |
6 | 7 |
8 |
9 | 10 |
11 |
12 | base{{value | noPrefix}} 13 |
14 |
15 | 16 |
17 |
18 | status{{data.status.value}} 19 |
20 |
21 | description 22 |
23 |
24 | reference 25 |
26 |
27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /frontend/inventory/schemas.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 | 6 |
7 |
8 |
9 | 10 | 11 | xParsing {{uploadSchema.value.replace("C:\\fakepath\\","")}} failed. 12 | xSchema {{uploadSchema.value.replace("C:\\fakepath\\","")}} successfully added. 13 | 14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 34 | 35 |
 namerevision
27 | x 31 | {{schema.name}}{{schema.revision}}
36 | 37 |
38 | -------------------------------------------------------------------------------- /frontend/common/pipes.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; 3 | 4 | @Pipe({name: 'noPrefix'}) 5 | export class NoPrefixPipe implements PipeTransform { 6 | transform(value: string): string { 7 | return value.slice(value.indexOf(':') + 1); 8 | } 9 | } 10 | 11 | @Pipe({name: 'prefixOnly'}) 12 | export class PrefixOnlyPipe implements PipeTransform { 13 | transform(value: string): string { 14 | return value.slice(0, value.indexOf(':')); 15 | } 16 | } 17 | 18 | @Pipe({name: 'patternHighlight'}) 19 | export class PatternHighlightPipe implements PipeTransform { 20 | constructor(private _sanitizer:DomSanitizer) {} 21 | 22 | transform(value: string): SafeHtml { 23 | let result = ''; 24 | for(let i = 0; i < value.length; i++) { 25 | if (value[i] == '(' || value[i] == '[' || value[i] == '{') { 26 | result = result.concat(`` + value[i] + ``); 27 | } else if (value[i] == ')' || value[i] == ']' || value[i] == '}') { 28 | let data = value[i]; 29 | if (i + 1 < value.length && (value[i+1] == '?' || value[i+1] == '+' || value[i+1] == '*')) { 30 | i; 31 | data = value.slice(i, i + 2); 32 | i++; 33 | } 34 | result = result.concat(`` + data + ``); 35 | } else { 36 | result = result.concat(value[i]); 37 | } 38 | } 39 | return this._sanitizer.bypassSecurityTrustHtml(result); 40 | } 41 | } -------------------------------------------------------------------------------- /frontend/yang/yang.component.scss: -------------------------------------------------------------------------------- 1 | @import '../netopeer-common'; 2 | @import '../inventory/inventory.component'; 3 | 4 | $colorLineHover: #e1e1e1; 5 | $colorLineSelected: #999999; 6 | 7 | .nav-button { 8 | height: 2em; 9 | cursor: pointer; 10 | margin: 0.3em; 11 | } 12 | 13 | .loading { 14 | text-align: center; 15 | margin: auto; 16 | width: 10em; 17 | div { 18 | margin: auto; 19 | width: 50px; 20 | } 21 | } 22 | 23 | .yang-infobox { 24 | display: block; 25 | cursor: default; 26 | } 27 | 28 | .yang-info-section-label, 29 | .yang-info-subsection-label, 30 | .yang-info-label { 31 | color: black; 32 | font-weight: 100; 33 | text-transform: uppercase; 34 | } 35 | 36 | .yang-info-section { 37 | display: flex; 38 | flex-direction: column; 39 | flex-wrap: wrap; 40 | width: 100%; 41 | padding-left: 2em; 42 | } 43 | 44 | .yang-info-subsection { 45 | .yang-info { 46 | padding-left: 2em; 47 | } 48 | } 49 | 50 | .yang-info, 51 | .yang-info-label, 52 | .yang-info-value { 53 | border: none; 54 | a { 55 | font-weight: normal; 56 | } 57 | } 58 | 59 | .yang-info { 60 | display: flex; 61 | padding: 5px; 62 | } 63 | .yang-info:hover { 64 | background-color: $colorLineHover; 65 | } 66 | 67 | .yang-info:nth-of-type(even), .yang-info-subsection:nth-of-type(even) { 68 | background: lighten($colorLineHover, 5%); 69 | } 70 | 71 | .yang-info-value { 72 | display: inline-block; 73 | } 74 | 75 | .yang-info-section-label, 76 | .yang-info-subsection-label, 77 | .yang-info-label { 78 | min-width: 10em; 79 | overflow: hidden; 80 | text-overflow: ellipsis; 81 | display: inline-block; 82 | } 83 | .yang-revision-label { 84 | font-style: italic; 85 | } 86 | 87 | .yang-subsection-container { 88 | margin-left: 2em; 89 | } 90 | 91 | .pattern { 92 | .selectedGroup { 93 | background-color: $colorLineSelected; 94 | } 95 | .bracket { 96 | } 97 | } 98 | 99 | -------------------------------------------------------------------------------- /vagrant/Ubuntu-release/lgui-config.ini: -------------------------------------------------------------------------------- 1 | [api] 2 | ; Enable debugging mode 3 | ; This sets logging to debug (most verbose) and enables Flask's debugging mode 4 | ; DEFAULT: false 5 | debug = true 6 | 7 | ; Specify host on which to run 8 | ; Use only in combination with running API directly as a package. Otherwise this setting is ignored. 9 | ; DEFAULT: localhost 10 | ;host = 11 | 12 | ; Specify port on which to run 13 | ; Use only in combination with running API directly as a package. Otherwise this setting is ignored. 14 | ; DEFAULT: 5555 15 | port = 5555 16 | threaded = true 17 | 18 | version = 1.0 19 | modules = /modules 20 | ssl = false 21 | 22 | ; Authorization Providers 23 | ; By default the only authorization provider is the database (defined below) 24 | ; Liberouter GUI can utilize other providers using Linux PAM (pluggable authentication modules). If 25 | ; you desire such feature just uncomment following line 26 | ; DEFAULT: false 27 | ;pam = true 28 | 29 | ; Enable Cross-Origin Resource Sharing 30 | ; WARNING: This feature is not recommended due to its security concerns. Try to use proxying in the 31 | ; first place. 32 | ; DEFAULT: false 33 | ;cors = true 34 | 35 | ; Session Timeout (seconds) 36 | ; States how long a session will be valid in seconds 37 | ; DEFAULT: 900 38 | ;session_timeout = 900 39 | 40 | ; Max User Session 41 | ; Limit number of sessions created by a single unique user 42 | ; DEFAULT: 10 43 | ;session_max_per_user = 10 44 | 45 | [database] 46 | ; possible values: sqlite, mysql, mongodb 47 | ; sqlite: file must be specified, the server and port are ignored 48 | ; mysql: server, port and database must be specified, user and password 49 | ; are for authentication to the db 50 | ; mongodb: server, port and database must be set 51 | provider = sqlite 52 | users = users 53 | configuration = configuration 54 | 55 | [mongodb] 56 | server = localhost 57 | port = 27017 58 | ;user = 59 | ;password = 60 | database = liberouter 61 | 62 | [sqlite] 63 | file = /var/www/html/ncgui/data/ncgui-users.sq3 64 | 65 | [ssl] 66 | ;key = 67 | ;certificate = 68 | -------------------------------------------------------------------------------- /backend/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Netopeer2 GUI backend 3 | File: __init__.py 4 | Author: Radek Krejci 5 | 6 | Backend initialization via liberouter GUI. 7 | """ 8 | 9 | from liberouterapi import config, modules 10 | 11 | # Get Netopeer backend config 12 | config.load(path = __path__[0] + '/config.ini') 13 | 14 | # Register a blueprint 15 | module_bp = modules.module.Module('netopeer', __name__, url_prefix = '/netopeer', no_version = True) 16 | 17 | from .schemas import * 18 | from .devices import * 19 | from .connections import * 20 | 21 | module_bp.add_url_rule('/inventory/schemas', view_func = schemas_list, methods = ['GET']) 22 | module_bp.add_url_rule('/inventory/schemas', view_func = schemas_add, methods=['POST']) 23 | module_bp.add_url_rule('/inventory/schemas', view_func = schemas_rm, methods = ['DELETE']) 24 | module_bp.add_url_rule('/inventory/schema', view_func = schema_get, methods = ['GET']) 25 | module_bp.add_url_rule('/inventory/devices/list', view_func = devices_list, methods=['GET']) 26 | module_bp.add_url_rule('/inventory/devices', view_func = devices_add, methods=['POST']) 27 | module_bp.add_url_rule('/inventory/devices', view_func = devices_rm, methods = ['DELETE']) 28 | module_bp.add_url_rule('/session', view_func = connect, methods=['POST']) 29 | module_bp.add_url_rule('/session', view_func = session_close, methods = ['DELETE']) 30 | module_bp.add_url_rule('/session/alive', view_func = session_alive, methods=['GET']) 31 | module_bp.add_url_rule('/session/capabilities', view_func = session_get_capabilities, methods=['GET']) 32 | module_bp.add_url_rule('/session/rpcGet', view_func = session_get, methods=['GET']) 33 | module_bp.add_url_rule('/session/commit', view_func = session_commit, methods = ['POST']) 34 | module_bp.add_url_rule('/session/element/checkvalue', view_func = data_checkvalue, methods = ['GET']) 35 | module_bp.add_url_rule('/session/schema', view_func = schema_info, methods = ['GET']) 36 | module_bp.add_url_rule('/session/schema/checkvalue', view_func = schema_checkvalue, methods = ['GET']) 37 | module_bp.add_url_rule('/session/schema/values', view_func = schema_values, methods = ['GET']) 38 | -------------------------------------------------------------------------------- /frontend/config/tree-edit.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | cancel 9 | done 14 | 22 | 27 |
28 | -------------------------------------------------------------------------------- /frontend/inventory/inventory.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, Input } from '@angular/core'; 2 | import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; 3 | 4 | @Component({ 5 | selector : 'netopeer-inventory', 6 | templateUrl : './inventory.component.html', 7 | styleUrls : ['./inventory.component.scss'] 8 | }) 9 | 10 | export class InventoryComponent { 11 | title = 'Inventory'; 12 | inventoryComponents = [ 13 | 'devices', 14 | 'schemas' 15 | ]; 16 | 17 | constructor() { } 18 | } 19 | 20 | @Component({ 21 | selector: 'ngbd-modal-content', 22 | styleUrls: ['../netopeer.scss'], 23 | template: ` 26 | 33 | ` 36 | }) 37 | export class DialogueSchema implements OnInit { 38 | @Input() info; 39 | password = ''; 40 | 41 | constructor(public activeModal: NgbActiveModal) { } 42 | 43 | upload(schema: File) { 44 | let reader = new FileReader(); 45 | 46 | console.log(schema); 47 | reader.onloadend = () => { 48 | //console.log(reader.result); 49 | this.activeModal.close({'filename': schema.name, 'data': reader.result}); 50 | }; 51 | reader.readAsText(schema); 52 | } 53 | 54 | ngOnInit(): void { 55 | document.getElementById('uploadSchema').focus(); 56 | } 57 | } -------------------------------------------------------------------------------- /frontend-assets/icons/show_children.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 64 | 65 | -------------------------------------------------------------------------------- /frontend-assets/icons/tree_empty.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 66 | 67 | -------------------------------------------------------------------------------- /frontend-assets/icons/show_children_active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 64 | 65 | -------------------------------------------------------------------------------- /frontend-assets/icons/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 64 | 69 | 70 | -------------------------------------------------------------------------------- /frontend-assets/icons/add_active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 64 | 69 | 70 | -------------------------------------------------------------------------------- /frontend-assets/icons/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 64 | 69 | 70 | -------------------------------------------------------------------------------- /frontend-assets/icons/close_active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 64 | 69 | 70 | -------------------------------------------------------------------------------- /frontend-assets/icons/show_all.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 64 | 69 | 70 | -------------------------------------------------------------------------------- /frontend-assets/icons/collapse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 64 | 69 | 70 | -------------------------------------------------------------------------------- /frontend-assets/icons/collapse_active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 64 | 69 | 70 | -------------------------------------------------------------------------------- /frontend-assets/icons/show_all_active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 64 | 69 | 70 | -------------------------------------------------------------------------------- /frontend-assets/icons/tree_root.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 30 | 32 | 54 | 57 | 58 | 65 | 71 | 72 | -------------------------------------------------------------------------------- /frontend-assets/icons/tree_last_branch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 21 | 23 | image/svg+xml 24 | 26 | 27 | 28 | 29 | 30 | 32 | 54 | 57 | 58 | 65 | 71 | 72 | -------------------------------------------------------------------------------- /frontend-assets/icons/tree_cont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 20 | 22 | 23 | 25 | image/svg+xml 26 | 28 | 29 | 30 | 31 | 32 | 34 | 56 | 59 | 60 | 67 | 73 | 74 | -------------------------------------------------------------------------------- /frontend-assets/icons/leaf.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 65 | 66 | -------------------------------------------------------------------------------- /frontend/netopeer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | 3 | import { SessionsService } from './config/sessions.service'; 4 | import { DevicesService } from './inventory/devices.service'; 5 | import { Device } from './inventory/device'; 6 | 7 | class NComponent { 8 | route: string; 9 | name: string; 10 | } 11 | 12 | const NCOMPONENTS: NComponent[] = [ 13 | { route : 'inventory', name: 'Inventory' }, 14 | { route : 'config', name: 'Configuration' }, 15 | { route : 'yang', name: 'YANG Explorer' }, 16 | { route : 'monitoring', name: 'Monitoring' }, 17 | { route : 'plugins', name: 'Plugins' } 18 | ]; 19 | 20 | @Component({ 21 | selector : 'netopeer', 22 | templateUrl : './netopeer.component.html', 23 | styleUrls : ['./netopeer.scss'], 24 | }) 25 | 26 | export class NetopeerComponent implements OnInit { 27 | componentTitle = ''; 28 | netopeerComponents = NCOMPONENTS; 29 | 30 | constructor(private sessionsService: SessionsService, 31 | private devicesService: DevicesService) { } 32 | 33 | ngOnInit() { 34 | /* autoconnect selected devices if needed */ 35 | if (localStorage.getItem('netopeer-autoconnect') == 'enabled') { 36 | let ac_sessions: number[] = []; /* currently connected autoconnect devices' ids */ 37 | for (let session of this.sessionsService.sessions) { 38 | if (session.device.autoconnect) { 39 | ac_sessions.push(session.device.id); 40 | } 41 | } 42 | let ac_devices: Device[] = []; /* devices with enabled autoconnect */ 43 | this.devicesService.getDevices().subscribe(devices => { 44 | for (let device of devices) { 45 | if (!device['autoconnect']) { 46 | continue; 47 | } 48 | let i = ac_sessions.indexOf(device.id); 49 | if (i != -1) { 50 | ac_sessions.splice(i, 1); 51 | continue; 52 | } 53 | /* we have not connected autoconnect device */ 54 | ac_devices.push(device); 55 | } 56 | for (let device of ac_devices) { 57 | this.sessionsService.connect(device).subscribe(); 58 | } 59 | localStorage.setItem('netopeer-autoconnect', 'done'); 60 | }); 61 | } 62 | } 63 | 64 | onActivate(componentRef) { 65 | this.componentTitle = componentRef.title; 66 | } 67 | onDeactivate(componentRef) { 68 | this.componentTitle = ''; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /frontend-assets/icons/menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 64 | 69 | 74 | 75 | -------------------------------------------------------------------------------- /frontend-assets/icons/menu_active.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 22 | 24 | image/svg+xml 25 | 27 | 28 | 29 | 30 | 31 | 33 | 55 | 58 | 59 | 64 | 69 | 74 | 75 | -------------------------------------------------------------------------------- /vagrant/Ubuntu-release/Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | $init = <