Angular JSON-RPC

Yet another method of reading the JSON-RPC interface.

The example below reads the result of the latest softwareAudit and displays it as a table. Information about installed sw products are merged in.

All needed files (.html, .js, .css) should be hosted below /usr/share/opsiconfd/static/AngularExample folder. This way no other Webserver is involved and cross scripting complaints by the browser are avoided.

The location would then read https://opsi:4447/AngularExample.

Prerequisites

Of course its helpful to load the angular-min.js (or angular.js for development) from an appropriate location. The example uses a local copy.

Minor Obstacles

opsiconfd will currently answer /rpc? requests with Encoding “deflate”, but will anounce it as “gzip”. This currently prevents any browser from even reading the opsiconfd answer. The responsible Worker.py needs a small change to answer requests from a browser and Winst.exe (and other legacy opsi clients) as well. The issue is known and a patch is mentioned in the forums.

Most requests require a logon, some even special rights. The script user should be member of a new group opsireader and the acl.conf file should be prepended with appropriate lines:

getProducts_.*		: sys_group(opsireader); sys_group(opsiadmin); opsi_depotserver; self; opsi_client
productOnClient_get.*	: sys_group(opsireader); sys_group(opsiadmin); opsi_depotserver;self; opsi_client
auditSoftwareOnClient_get.*	: sys_group(opsireader); sys_group(opsiadmin); opsi_depotserver; self; opsi_client
product_get.*		: sys_group(opsireader); sys_group(opsiadmin); opsi_depotserver; self; opsi_client

Calling Parameter

When called as /AngularExample/?{“dns”:“clientID.domain.local”} the SoftwareAudit of the given clientId is displayed. OPSI client names may be entered in an input field at any time and are queried asynchronously.

The strange format of the query string as json has its reason: it is needed for the AndroidApp named OPSI Admin. The App can read barcodes/QRcodes formatted this way in order to open the details of the identified client. Nice feature when the barcode sticks on the client.

What if the barcode appears right on the client's screen? Try this Java app: Opsi-Barcode

The Code

/usr/share/opsiconfd/static/AngularExample
  ├ index.html
  ├ angular.js
  ├ swinv.js
  └ swinv.css
 

index.html

Where all other is loaded from..

<!DOCTYPE html>
<html ng-app="opsi-swinv">
<head>
    <link rel="stylesheet" type="text/css" href="swinv.css" >
    <script type="text/javascript" src="angular.js"></script> <!-- chenge to angular-min for production -->
    <!-- script type="text/javascript" src="angular-resource.js"></script -->
    <script type="text/javascript" src="swinv.js"></script>
    <script type="text/javascript" src="swpattern.js"></script>
</head>
 
<body ng-controller="swinvController as sw">
 
<div class="swlist">
 
<input id="inputId" ng-model="sw.clientId"></input>
<button ng-click="sw.refresh(sw.clientId)">refresh list</button>
 
 
<table border >
  <tr><th>Name</th><th>Version</th><th>Lizenz</th><th>Installationsgrund</th></tr>
 
<tr ng-repeat="swp in sw.products" ng-show="sw.nonMS(swp.name)">
<td>{{swp.name}} </td>
<td> {{swp.version}}</td>
<td>{{sw.reason(swp.name)}}</td>
<td class={{sw.csswarn(swp.name)}}><span ng-show="!sw.isOpsi(swp.name)">{{sw.reason(swp.name)}} </span>{{sw.opsiProdId(swp.name)}}</td>
</tr>
</table>
 
<br/><label><input type="checkbox" ng-model="sw.showopsiinstalled" />installed OPSI products on this client <span>({{sw.clientId}})</span></label>
<div ng-show="sw.showopsiinstalled">
<table border>
  <tr><th>OpsiProduct</th></tr>
  <tr ng-repeat="inst in sw.installed">
    <td>{{inst}}</td>
  </tr>
</table>
</div>
 
<br/><label><input type="checkbox" ng-model="sw.showallprods" />available products</label>
<table ng-show="sw.showallprods" border width="100%">
  <tr><th>productInfo</th><th>productId</th><th>...............................................</th></tr>
  <tr ng-repeat="prod in sw.productinfos">
 
    <td>{{prod.productId}}</td>
    <td>{{prod.name}}</td>
    <td></td>
  </tr>
</table>
 
</div>
</body>
</html>

swinv.js

The AngularJS mimic..

(function(){
    var app = angular.module('opsi-swinv', []);
 
    app.controller('swinvController',function($http,$filter){
 
	this.showallprods=0;
	this.showopsiinstalled=0;
	baseurl='/rpc';
	logonurl='https://testerMemberOfGroupopsireader:unGuessablePassword@'+window.location.host+'/rpc';
 
	this.clientId="defaultclient.domain.local";
 
	this.auditParams={'id':'jsonrpc','method':'auditSoftwareOnClient_getObjects','params':[]};
	this.auditParams.params.push([]);
	this.auditParams.params.push({"clientId":"defaultclient.domain.local"});
 
	me=this;
	me['installed']=[];
	me['productinfos']=[];
 
	this.jsonParmDns=function(){
	    this.search=window.location.search;
	    if (this.search.match(/dns/)){ // we believe in barcode like {"dns":"<clientId>"}
		var qsearch=decodeURIComponent(this.search).match(/{.+dns.+:.+}/);
		var jsonsearch=decodeURIComponent(qsearch); // get the json 
		return angular.fromJson(jsonsearch).dns;
//		return JSON.parse(jsonsearch).dns;
	    }
	    return null;
	};
 
	this.opsicall=function(field,method,arg){
    	    Params={'id':'1','method':method,'params':[]};
	    if(arg)Params.params[0]=arg;
	    $http.post(baseurl,Params)
		.success(function(response,stat,head,conf){
//		    alert("success: "+$filter('json')(response));
		    me.opsires="success: "+method;
		    me.opsiconf=conf;
		    if(response.error)
			alert(response.error.message);
		    else
			me[field]=response.result;
 
		})
		.error(function(err,stat,head,conf){
		    me.opsires="error";
		    alert("err: "+(err)+stat+head+$filter('json')(conf));
		    me.opsistat=stat;
		    me.opsihead=head;
		    me.opsiconf=conf;
		});
 
	};
 
	this.logon=function(){
	    $http.post(logonurl,this.logonParams)
		.success(function(response){
//		    alert("success: "+$filter('json')(response));
		    // call after logon success
		    me.getproductinfos(); 
		    me.refresh(me.clientId);
		})
		.error(function(response){
		    alert("err: "+(response));
		});
	};
 
	this.getprods=function(id){
	    this.opsires="calling.. "+id;
	    if(id)this.auditParams.params[1].clientId=id;
	    $http.post(baseurl,this.auditParams)
		.success(function(response,stat,head,conf){
		   // alert("success: "+response);
		    if(response.error)alert(response.error.message);
		    me.products=response.result;
		    me.opsistat=stat;
		    me.opsihead=head;
		    me.opsiconf=conf;
		})
		.error(function(err,stat,head,conf){
		    me.opsires="error";
		    alert("err: "+(err)+stat+head+$filter('json')(conf));
		    me.opsistat=stat;
		    me.opsihead=head;
		    me.opsiconf=conf;
		});
 
	};
 
	// get products on Client with id
	this.getinstalled=function(id){
	    this.opsicall('installed','getInstalledLocalBootProductIds_list',id);
	};
 
	// get all available OPSi products
	this.getproductinfos=function(){
	    this.opsicall('productinfos','getProducts_listOfHashes');
	};
 
	this.logon();
	this.clientId=this.jsonParmDns();
//	this.getproductinfos(); 
 
	this.refresh=function(id){
	    if(null != id){
		this.getprods(id);
		this.getinstalled(id);
	    }
	};
 
 
	this.nonMS=function(name){
//	    return true;
	    if(name.match(/AddressBook/)) return false;
	    if(name.match(/^Connection Manager/)) return false;
	    if(name.match(/^DirectDrawEx/)) return false;
	    if(name.match(/^DXM_Runtime/)) return false;
	    if(name.match(/^Fontcore/)) return false;
	    if(name.match(/KB[0-9]+/))  return false;
	    if(name.match(/Microsoft/)) return false;
	    if(name.match(/^MPlayer2/)) return false;
	    if(name.match(/^Outlook/)) return false;
	    if(name.match(/^[iI][Ee]/)) return false;
	    if(name.match(/^Windows/)) return false;
	    if(name.match(/^MobileOptionPack/)) return false;
	    if(name.match(/^SchedulingAgent/)) return false;
	    if(name.match(/^WIC/)) return false;
	    return true;
	}
 
 
	this.pattern=swpattern;
 
	this.reason=function(name){
	    for(p in this.pattern){
		if(name.match(p))
		    return this.pattern[p];
	    }
 
	};
 
	// guess the corresponding OPSI productId from the Windows Product name
	this.opsiProdId=function(name){
	    for(i=0; i<this.productinfos.length;i++){
		p=this.productinfos[i];
		if(p.name && name.match(p.name)||name.match(p.productId))
		    return p.productId;
	    }
	    return null;
	};
	// convenience: is this an OPSI product?
	this.isOpsi=function(name){
	    return (null != this.opsiProdId(name));
	};
 
	// mark rows w/o reason/license
	this.csswarn=function(name){
	    if(this.isOpsi(name) || this.reason(name))
		return "ng-binding";
	    else
		return "warn";
	};
 
 
    });
 
 
    var swpattern={'^Audacity':'OpenSource',
		   '^Java':'Oracle',
		   '^Mozilla ':'Mozilla Public',
		   '^OpenOff':'OpenSource',
		   '^Google':'Google Public',
		   '^Adobe Flash':'Adobe Public',
		   '^OpenSSL':'OpenSource',
		   '^Python':'OpenSource'
 
		  };
 
 
 
 
})();

swinv.css

some tweaking of the appearance - anything goes..

body {
    font-family: "Comic Sans M$", Arial, Helvetica, sans-serif;
}
 
table{
    width: 100%;
    border-collapse: collapse;
}
 
td, th {
    font-size: 1em;
    border: 1px solid #aa0093;
    padding: 3px 7px 2px 7px;
}
 
th {
    font-size: 1.1em;
    text-align: left;
    padding-top: 5px;
    padding-bottom: 4px;
    background-color: #d30c6b;
    color: #ffffff;
}
 
label{
    background-color: #d30c6b;
    color: #ffffff;
}
 
 
td.warn {
/* enhance unlicensed products*/
    color: #000000;
    background-color: yellow;
}
 
.ng-binding{
//    background-color: blue;
font-family: Courier;
}

QR Code
QR Code userspace:json-rpc_angularjs (generated for current page)