Skip to content

Commit 52e8e32

Browse files
committed
gui FEATURE ask for missing schema when connecting to a device
When the backend is unable to get schema used by the device from the local inventory as well as via get-schema from the server itself, ask user (frontend) to upload the missing schema.
1 parent 025c67f commit 52e8e32

File tree

4 files changed

+105
-9
lines changed

4 files changed

+105
-9
lines changed

backend/connections.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ def connect_sio_send(data):
3838

3939
@socketio.on('device_auth_password')
4040
@socketio.on('hostcheck_result')
41-
def hostkey_check_answer(data):
41+
@socketio.on('getschema_result')
42+
def process_answer(data):
4243
connect_sio_send(data)
4344

4445

@@ -88,7 +89,6 @@ def auth_common(session_id):
8889
# wait for response from the frontend
8990
e = connect_sio_data[session_id] = event.Event()
9091
data = e.wait()
91-
print(data)
9292
result = data['password']
9393
except Timeout:
9494
# no response received within the timeout
@@ -105,17 +105,56 @@ def auth_common(session_id):
105105

106106

107107
def auth_password(username, hostname, priv):
108-
print("auth_password callback")
109108
socketio.emit('device_auth', {'id': priv, 'type': 'Password Authentication', 'msg': username + '@' + hostname}, callback = connect_sio_send)
110109
return auth_common(priv)
111110

112111

113112
def auth_interactive(name, instruction, prompt, priv):
114-
print("auth_interactive callback")
115113
socketio.emit('device_auth', {'id': priv, 'type': name, 'msg': instruction, 'prompt': prompt}, callback = connect_sio_send)
116114
return auth_common(priv)
117115

118116

117+
def getschema(name, revision, submod_name, submod_revision, priv):
118+
# ask frontend/user for missing schema
119+
params = {'id': priv['session_id'], 'name' : name, 'revision' : revision, 'submod_name' : submod_name, 'submod_revision' : submod_revision}
120+
socketio.emit('getschema', params, callback = connect_sio_send)
121+
122+
result = (None, None)
123+
timeout = Timeout(300)
124+
try:
125+
# wait for response from the frontend
126+
e = connect_sio_data[priv['session_id']] = event.Event()
127+
data = e.wait()
128+
if data['filename'].lower()[len(data['filename']) - 5:] == '.yang':
129+
format = yang.LYS_IN_YANG
130+
elif data['filename'].lower()[len(data['filename']) - 4:] == '.yin':
131+
format = yang.LYS_IN_YIN
132+
else:
133+
return result
134+
result = (format, data['data'])
135+
except Timeout:
136+
# no response received within the timeout
137+
log.info("socketio: getschema timeout.")
138+
except (KeyError, AttributeError) as e:
139+
# invalid response
140+
log.error(e)
141+
log.error("socketio: invalid getschema_result received.")
142+
finally:
143+
# we have the response
144+
connect_sio_data.pop(priv['session_id'], None)
145+
timeout.cancel()
146+
147+
# store the received file
148+
try:
149+
with open(os.path.join(INVENTORY, priv['user'].username, data['filename']), 'w') as schema_file:
150+
schema_file.write(data['data'])
151+
except Exception as e:
152+
log.error(e)
153+
pass
154+
155+
return result
156+
157+
119158
@auth.required()
120159
def connect():
121160
session = auth.lookup(request.headers.get('lgui-Authorization', None))
@@ -136,6 +175,7 @@ def connect():
136175
raise NetopeerException('Unknown device to connect to request.')
137176

138177
nc.setSearchpath(path)
178+
nc.setSchemaCallback(getschema, session)
139179

140180
if 'password' in device:
141181
ssh = nc.SSH(device['username'], password = device['password'])
@@ -148,7 +188,9 @@ def connect():
148188
try:
149189
ncs = nc.Session(device['hostname'], device['port'], ssh)
150190
except Exception as e:
191+
nc.setSchemaCallback(None)
151192
return(json.dumps({'success': False, 'error-msg': str(e)}))
193+
nc.setSchemaCallback(None)
152194

153195
if not user.username in sessions:
154196
sessions[user.username] = {}

frontend/inventory/devices.component.ts

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,12 @@ export class InventoryDevicesComponent implements OnInit {
125125
}
126126
}
127127

128-
socketAnswer(event: string, id:string, item: string, value: any) {
128+
socketAnswer(event: string, id:string, item: string, value: any, item2: string = null, value2: any = null) {
129129
let data = {'id': id};
130130
data[item] = value;
131+
if (item2) {
132+
data[item2] = value2
133+
}
131134
this.socketService.send(event, data);
132135
}
133136

@@ -165,6 +168,16 @@ export class InventoryDevicesComponent implements OnInit {
165168
});
166169
});
167170

171+
this.socketService.subscribe('getschema').subscribe((message: any) => {
172+
let modalRef = this.modalService.open(DialogueSchema, {centered: true, backdrop: 'static', keyboard: false});
173+
modalRef.componentInstance.info = message;
174+
modalRef.result.then((result) => {
175+
this.socketAnswer('getschema_result', message['id'], 'filename', result['filename'], 'data', result['data']);
176+
}, (reason) => {
177+
this.socketAnswer('getschema_result', message['id'], 'filename', '', 'data', '');
178+
});
179+
});
180+
168181
this.sessionsService.connect(device).subscribe(result => {
169182
if (result['success']) {
170183
this.router.navigateByUrl('/netopeer/config');
@@ -226,4 +239,43 @@ export class DialoguePassword implements OnInit {
226239
ngOnInit(): void {
227240
document.getElementById('device_password').focus();
228241
}
229-
}
242+
}
243+
244+
@Component({
245+
selector: 'ngbd-modal-content',
246+
styleUrls: ['../netopeer.scss'],
247+
template: `<div class="modal-header">
248+
<h4 class="modal-title">Missing YANG Schema</h4>
249+
</div>
250+
<div class="modal-body">
251+
<label>The device utilize YANG schema <b>{{info.name}}</b> <ng-container *ngIf="info.revision">in revision <b>{{info.revision}}</b></ng-container>.<br/>
252+
<span *ngIf="!info['submod_name']">Please provide this schema.</span>
253+
<span *ngIf="info['submod_name']">Please provide submodule <b>{{info['submod_name']}}</b> <ng-container *ngIf="info['submod_revision']">in revision <b>{{info['submod_revision']}}</b></ng-container> for this schema.</span>
254+
</label><br/>
255+
<input id="uploadSchema" #uploadSchema type="file" (change)="upload(uploadSchema.files[0])" name="schema" placeholder="Upload schema"/>
256+
</div>
257+
<div class="modal-footer">
258+
<button class="btn btn-light" (click)="activeModal.dismiss(null)">cancel</button>
259+
</div>`
260+
})
261+
export class DialogueSchema implements OnInit {
262+
@Input() info;
263+
password = '';
264+
265+
constructor(public activeModal: NgbActiveModal) { }
266+
267+
upload(schema: File) {
268+
let reader = new FileReader();
269+
270+
console.log(schema);
271+
reader.onloadend = () => {
272+
//console.log(reader.result);
273+
this.activeModal.close({'filename': schema.name, 'data': reader.result});
274+
};
275+
reader.readAsText(schema);
276+
}
277+
278+
ngOnInit(): void {
279+
document.getElementById('uploadSchema').focus();
280+
}
281+
}

frontend/inventory/schemas.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<div *ngIf="addingSchema">
77
<hr/>
88
<div class="buttons">
9-
<input #uploadSchema type="file" (change)="upload(uploadSchema.files[0])" name="schema" placeholder="Upload schema"/>
9+
<input #uploadSchema type="file" (change)="upload(uploadSchema.files[0])" name="schema" placeholder="Upload schema"/>
1010
<span [ngSwitch]="addingResult">
1111
<span *ngSwitchCase="0" class="msg-rounded msg-failure"><span class="msg-close" (click)="addingResult=-1">x</span>Parsing {{uploadSchema.value.replace("C:\\fakepath\\","")}} failed.</span>
1212
<span *ngSwitchCase="1" class="msg-rounded msg-success"><span class="msg-close" (click)="addingResult=-1">x</span>Schema {{uploadSchema.value.replace("C:\\fakepath\\","")}} successfully added.</span>

frontend/netopeer.module.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import { NetopeerComponent } from './netopeer.component';
1919
import { DashboardComponent } from './dashboard.component';
2020
import { InventoryComponent } from './inventory/inventory.component';
2121
import { InventorySchemasComponent } from './inventory/schemas.component';
22-
import { InventoryDevicesComponent, DialogueHostcheck, DialoguePassword } from './inventory/devices.component';
22+
import { InventoryDevicesComponent, DialogueHostcheck, DialoguePassword, DialogueSchema } from './inventory/devices.component';
2323
import { ConfigComponent } from './config/config.component';
2424
import { TreeView, TreeNode, TreeLeaflistValue, TreeIndent, TreeCreate, TreeEdit, TreeScrollTo, CheckLeafValue } from './config/tree.component';
2525
import { YANGComponent, YANGModule, YANGIdentity, YANGFeature, YANGTypedef, YANGType, YANGRestriction, YANGNode, YANGIffeature } from './yang/yang.component';
@@ -126,6 +126,7 @@ const routes: Routes = [
126126
PluginsComponent,
127127
DialogueHostcheck,
128128
DialoguePassword,
129+
DialogueSchema,
129130
NoPrefixPipe,
130131
PrefixOnlyPipe,
131132
PatternHighlightPipe
@@ -139,7 +140,8 @@ const routes: Routes = [
139140
entryComponents : [
140141
NetopeerComponent,
141142
DialogueHostcheck,
142-
DialoguePassword
143+
DialoguePassword,
144+
DialogueSchema
143145
]
144146
})
145147
export class NetopeerModule { }

0 commit comments

Comments
 (0)