Hide secret keys in diff.html

This commit is contained in:
maudoux 2019-04-01 14:42:25 +02:00
parent cc0dc83d3d
commit b6162f5ae9
17 changed files with 726 additions and 16 deletions

View File

@ -40,8 +40,9 @@ useRedirectOnError = 0
enabledModules = conf, sessions, notifications, 2ndFA, viewer
protection = manager
viewerHiddenPK = samlIDPMetaDataNodes samlSPMetaDataNodes portalDisplayLogout
viewerHiddenPK = samlIDPMetaDataNodes samlSPMetaDataNodes portalDisplayLogout captcha_login_enabled
viewerAllowBrowser = 1
viewerAllowDiff = 1
staticPrefix = /static
languages = fr, en, vi, ar, de, it, zh

View File

@ -364,6 +364,7 @@ enabledModules = conf, sessions, notifications, 2ndFA, viewer
; Viewer options - Default values
;viewerHiddenPK = samlIDPMetaDataNodes samlSPMetaDataNodes
;viewerAllowBrowser = 0
;viewerAllowDiff = 0
;[node-handler]
;

View File

@ -24,6 +24,7 @@ extends 'Lemonldap::NG::Common::Conf::AccessLib',
has csp => ( is => 'rw' );
has brw => ( is => 'rw', default => 0 );
has dif => ( is => 'rw', default => 0 );
## @method boolean init($args)
# Launch initialization method
@ -88,6 +89,7 @@ sub init {
"default-src 'self' $portal;frame-ancestors 'none';form-action 'self';"
);
$self->brw( $self->{viewerAllowBrowser} || $conf->{viewerAllowBrowser} );
$self->dif( $self->{viewerAllowDiff} || $conf->{viewerAllowDiff} );
$self->defaultRoute( $working[0]->defaultRoute );
# Find out more glyphicones at https://www.w3schools.com/icons/bootstrap_icons_glyphicons.asp
@ -135,7 +137,7 @@ sub init {
sub tplParams {
my ($self) = @_;
return ( VERSION => $VERSION, ALLOWBROWSER => $self->brw );
return ( VERSION => $VERSION, ALLOWBROWSER => $self->brw, ALLOWDIFF => $self->dif );
}
sub javascript {

View File

@ -3566,6 +3566,10 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-
'default' => 0,
'type' => 'bool'
},
'viewerAllowDiff' => {
'default' => 0,
'type' => 'bool'
},
'viewerHiddenPK' => {
'default' => 'samlIDPMetaDataNodes samlSPMetaDataNodes',
'type' => 'text'

View File

@ -927,6 +927,11 @@ sub attributes {
default => 0,
documentation => 'Allow configuration browser',
},
viewerAllowDiff => {
type => 'bool',
default => 0,
documentation => 'Allow configuration diff',
},
# Notification
oldNotifFormat => {

View File

@ -70,8 +70,9 @@ sub addRoutes {
$self->addRoute( view => { ':cfgNum' => { '*' => 'getKey' } }, ['GET'] )
# Difference between confs
->addRoute( diff => { ':conf1' => { ':conf2' => 'diff' } } )
->addRoute( 'diff.html', undef, ['GET'] );
->addRoute(
view => { diff => { ':conf1' => { ':conf2' => 'viewDiff' } } } )
->addRoute( 'viewDiff.html', undef, ['GET'] );
}
sub getConfByNum {
@ -79,9 +80,44 @@ sub getConfByNum {
$self->SUPER::getConfByNum( $cfgNum, @args );
}
sub diff {
sub viewDiff {
my ( $self, $req, @path ) = @_;
$self->SUPER::diff( $req, @path );
return $self->sendError( $req, 'to many arguments in path info', 400 )
if (@path);
my @cfgNum =
( scalar( $req->param('conf1') ), scalar( $req->param('conf2') ) );
my @conf;
$self->logger->debug(" Loading confs");
# Load the 2 configurations
for ( my $i = 0 ; $i < 2 ; $i++ ) {
if ( %{ $self->currentConf }
and $cfgNum[$i] == $self->currentConf->{cfgNum} )
{
$conf[$i] = $self->currentConf;
}
else {
$conf[$i] = $self->confAcc->getConf(
{ cfgNum => $cfgNum[$i], raw => 1, noCache => 1 } );
return $self->sendError(
$req,
"Configuration $cfgNum[$i] not available $Lemonldap::NG::Common::Conf::msg",
400
) unless ( $conf[$i] );
}
}
require Lemonldap::NG::Manager::Conf::Diff;
my @res =
$self->Lemonldap::NG::Manager::Conf::Diff::diff( $conf[0], $conf[1] );
my $hiddenKeys = $self->{viewerHiddenPK} || '';
$self->logger->debug("Deleting hidden Conf keys...");
foreach ( split /\s+/, $hiddenKeys ) {
$self->logger->debug("Delete hidden Conf keys");
$self->logger->debug("-> Delete $_");
delete $res[0]->{$_};
delete $res[1]->{$_};
}
return $self->sendJSONresponse( $req, [@res] );
}
sub rejectKey {

View File

@ -0,0 +1,215 @@
###
diff.html script
###
llapp = angular.module 'llngConfDiff', ['ui.tree', 'ui.bootstrap', 'llApp', 'ngCookies'] , ($rootScopeProvider) -> $rootScopeProvider.digestTtl(15)
llapp.controller 'DiffCtrl', [ '$scope', '$http', '$q', '$translator', '$location', ($scope, $http, $q, $translator, $location) ->
$scope.links = links
$scope.menulinks = menulinks
$scope.staticPrefix = staticPrefix
$scope.scriptname = scriptname
#$scope.formPrefix = formPrefix
$scope.availableLanguages = availableLanguages
$scope.waiting = true
$scope.showM = false
$scope.cfg = []
$scope.data = {}
$scope.currentNode = null
# Import translations functions
$scope.translateTitle = (node) ->
return $translator.translateField node, 'title'
$scope.translateP = $translator.translateP
$scope.translate = $translator.translate
$scope.toggle = (scope) ->
scope.toggle()
$scope.stoggle = (scope,node) ->
$scope.currentNode = node
scope.toggle()
# Handle menu items
$scope.menuClick = (button) ->
if button.popup
window.open button.popup
else
button.action = button.title unless button.action
switch typeof button.action
when 'function'
button.action $scope.currentNode, $scope
when 'string'
$scope[button.action]()
else
console.log typeof button.action
$scope.showM = false
# Function to change interface language
$scope.getLanguage = (lang) ->
$scope.lang = lang
$scope.init()
$scope.showM = false
# function `getCfg(b,n)`:
# Download configuration metadatas
#
#@param b local conf (0 or 1)
#@param n cfgNumber
getCfg = (b,n) ->
d = $q.defer()
if not $scope.cfg[b]? or $scope.cfg[b] != n
$http.get("#{confPrefix}#{n}").then (response) ->
if response and response.data
$scope.cfg[b] = response.data
date = new Date response.data.cfgDate * 1000
$scope.cfg[b].date = date.toLocaleString()
console.log "Metadatas of cfg #{n} loaded"
d.resolve 'OK'
else
d.reject response
, (response) ->
console.log response
d.reject 'NOK'
else
d.resolve()
return d.promise
# Intialization function
# Simply set $scope.waiting to false during $translator and tree root
# initialization
init = ->
$scope.message = null
$scope.currentNode = null
d = $q.defer()
$http.get("#{scriptname}view/diff/#{$scope.cfg[0].cfgNum}/#{$scope.cfg[1].cfgNum}").then (response) ->
data = []
data = readDiff(response.data[0],response.data[1])
$scope.data = buildTree(data)
$scope.message = ''
$scope.waiting = false
, (response) ->
$scope.message = "#{$scope.translate('error')} : #{response.statusLine}"
readDiff = (c1,c2,tr=true) ->
res = []
for k,v of c1
if tr
tmp =
title: $scope.translate(k)
id: k
else
tmp = title: k
unless k.match /^cfg(?:Num|Log|Author(?:IP)?|Date)$/
if v? and typeof v == 'object'
if v.constructor == 'array'
tmp.oldvalue = v
tmp.newvalue = c2[k]
else if typeof c2[k] == 'object'
tmp.nodes = readDiff c1[k],c2[k], false
else
tmp.oldnodes = toNodes v, 'old'
else
tmp.oldvalue = v
tmp.newvalue = c2[k]
res.push tmp
for k,v of c2
unless (k.match /^cfg(?:Num|Log|Author(?:IP)?|Date)$/) or c1[k]?
if tr
tmp =
title: $scope.translate(k)
id: k
else
tmp = title: k
if v? and typeof v == 'object'
if v.constructor == 'array'
tmp.newvalue = v
else
console.log "Iteration"
tmp.newnodes = toNodes v, 'new'
else
tmp.newvalue = v
res.push tmp
return res
toNodes = (c,s) ->
res = []
for k,v of c
tmp = title:k
if typeof v == 'object'
if v.constructor == 'array'
tmp["#{s}value"] = v
else
tmp["#{s}nodes"] = toNodes c[k], s
else
tmp["#{s}value"] = v
res.push tmp
return res
reverseTree = []
buildTree = (data) ->
return data unless reverseTree?
res = []
for elem in data
offset = res
path = if reverseTree[elem.id]? then reverseTree[elem.id].split '/' else ''
for node in path
if node.length > 0
if offset.length
found = -1
for n,i in offset
if n.id == node
#offset = n.nodes
found = i
if found != -1
offset = offset[found].nodes
else
offset.push
id: node
title: $scope.translate node
nodes: []
offset = offset[offset.length-1].nodes
else
offset.push
id: node
title: $scope.translate node
nodes: []
offset = offset[0].nodes
offset.push elem
return res
$scope.newDiff = ->
$location.path("/#{$scope.cfg[0].cfgNum}/#{$scope.cfg[1].cfgNum}")
pathEvent = (event, next, current) ->
n = next.match(new RegExp('#!?/(latest|[0-9]+)(?:/(latest|[0-9]+))?$'))
if n == null
$location.path '/latest'
else
$scope.waiting = true
$q.all [
$translator.init $scope.lang
$http.get("#{staticPrefix}reverseTree.json").then (response) ->
reverseTree = response.data
console.log "Structure loaded"
getCfg 0, n[1]
getCfg 1, n[2] if n[2]?
]
.then ->
if n[2]?
init()
else
if $scope.cfg[0].prev
$scope.cfg[1] = $scope.cfg[0]
getCfg 0, $scope.cfg[1].prev
.then ->
init()
else
$scope.data = []
$scope.waiting = false
, ->
$scope.message = $scope.translate('error')
$scope.waiting = false
true
$scope.$on '$locationChangeSuccess', pathEvent
]

View File

@ -27,7 +27,7 @@ llapp.controller 'TreeCtrl', [
$scope.waiting = true
$scope.showM = false
$scope.showT = false
$scope.form = 'home'
$scope.form = 'homeViewer'
$scope.currentCfg = {}
$scope.viewPrefix = window.viewPrefix
$scope.message = {}
@ -133,7 +133,7 @@ llapp.controller 'TreeCtrl', [
# Display main form
$scope.home = ->
$scope.form = 'home'
$scope.form = 'homeViewer'
$scope.showM = false
# Download raw conf
@ -438,7 +438,7 @@ llapp.controller 'TreeCtrl', [
title: 'emptyConf'
message: '__zeroConfExplanations__'
$scope.showModal 'message.html'
$scope.form = 'home'
$scope.form = 'homeViewer'
$scope.waiting = false
, readError
# Colorized link

View File

@ -0,0 +1,49 @@
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<span ng-if="!currentCfg.next" trspan="currentConfiguration"></span>
<span ng-if="currentCfg.next" trspan="loadedConfiguration"></span>
<i ng-if="currentCfg.prev">(<a trspan="diffWithPrevious" target="_blank" href="{{scriptname}}/viewDiff.html#!/{{currentCfg.cfgNum}}" role="link"></a>)</i>
</h3>
</div>
<table class="table table-striped">
<tr>
<th><span trspan="number"></span></th>
<td>
<span id="cfgnum" class="label label-success" comment="{{translateP('__newCfgAvailable__')}}" ng-class="{'label-warning':currentCfg.next}">{{currentCfg.cfgNum}}</span>
</td>
</tr>
<tr ng-if="currentCfg.cfgAuthor">
<th><span trspan="author"></span></th>
<td>{{currentCfg.cfgAuthor}}</td>
</tr>
<tr ng-if="currentCfg.cfgAuthorIP">
<th><span trspan="authorIPAddress"></span></th>
<td>{{currentCfg.cfgAuthorIP}}</td>
</tr>
<tr ng-if="currentCfg.cfgDate">
<th><span trspan="date"></span></th>
<td>{{currentCfg.date}}</td>
</tr>
<tr ng-if="currentCfg.cfgVersion">
<th><span trspan="cfgVersion"></span></th>
<td>{{currentCfg.cfgVersion}}</td>
</tr>
<tr ng-if="currentCfg.cfgLog">
<th><span trspan="cfgLog"></span></th>
<td id="cfglog">{{currentCfg.cfgLog}}</td>
</tr>
</table>
</div>
<script type="text/menu">
[{
"title": "downloadIt",
"action": "downloadConf",
"icon": "export"
},{
"title": "restore",
"icon": "import"
}]
</script>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,274 @@
// Generated by CoffeeScript 1.12.7
/*
diff.html script
*/
(function() {
var llapp;
llapp = angular.module('llngConfDiff', ['ui.tree', 'ui.bootstrap', 'llApp', 'ngCookies'], function($rootScopeProvider) {
return $rootScopeProvider.digestTtl(15);
});
llapp.controller('DiffCtrl', [
'$scope', '$http', '$q', '$translator', '$location', function($scope, $http, $q, $translator, $location) {
var buildTree, getCfg, init, pathEvent, readDiff, reverseTree, toNodes;
$scope.links = links;
$scope.menulinks = menulinks;
$scope.staticPrefix = staticPrefix;
$scope.scriptname = scriptname;
$scope.availableLanguages = availableLanguages;
$scope.waiting = true;
$scope.showM = false;
$scope.cfg = [];
$scope.data = {};
$scope.currentNode = null;
$scope.translateTitle = function(node) {
return $translator.translateField(node, 'title');
};
$scope.translateP = $translator.translateP;
$scope.translate = $translator.translate;
$scope.toggle = function(scope) {
return scope.toggle();
};
$scope.stoggle = function(scope, node) {
$scope.currentNode = node;
return scope.toggle();
};
$scope.menuClick = function(button) {
if (button.popup) {
window.open(button.popup);
} else {
if (!button.action) {
button.action = button.title;
}
switch (typeof button.action) {
case 'function':
button.action($scope.currentNode, $scope);
break;
case 'string':
$scope[button.action]();
break;
default:
console.log(typeof button.action);
}
}
return $scope.showM = false;
};
$scope.getLanguage = function(lang) {
$scope.lang = lang;
$scope.init();
return $scope.showM = false;
};
getCfg = function(b, n) {
var d;
d = $q.defer();
if (($scope.cfg[b] == null) || $scope.cfg[b] !== n) {
$http.get("" + confPrefix + n).then(function(response) {
var date;
if (response && response.data) {
$scope.cfg[b] = response.data;
date = new Date(response.data.cfgDate * 1000);
$scope.cfg[b].date = date.toLocaleString();
console.log("Metadatas of cfg " + n + " loaded");
return d.resolve('OK');
} else {
return d.reject(response);
}
}, function(response) {
console.log(response);
return d.reject('NOK');
});
} else {
d.resolve();
}
return d.promise;
};
init = function() {
var d;
$scope.message = null;
$scope.currentNode = null;
d = $q.defer();
return $http.get(scriptname + "view/diff/" + $scope.cfg[0].cfgNum + "/" + $scope.cfg[1].cfgNum).then(function(response) {
var data;
data = [];
data = readDiff(response.data[0], response.data[1]);
$scope.data = buildTree(data);
$scope.message = '';
return $scope.waiting = false;
}, function(response) {
return $scope.message = ($scope.translate('error')) + " : " + response.statusLine;
});
};
readDiff = function(c1, c2, tr) {
var k, res, tmp, v;
if (tr == null) {
tr = true;
}
res = [];
for (k in c1) {
v = c1[k];
if (tr) {
tmp = {
title: $scope.translate(k),
id: k
};
} else {
tmp = {
title: k
};
}
if (!k.match(/^cfg(?:Num|Log|Author(?:IP)?|Date)$/)) {
if ((v != null) && typeof v === 'object') {
if (v.constructor === 'array') {
tmp.oldvalue = v;
tmp.newvalue = c2[k];
} else if (typeof c2[k] === 'object') {
tmp.nodes = readDiff(c1[k], c2[k], false);
} else {
tmp.oldnodes = toNodes(v, 'old');
}
} else {
tmp.oldvalue = v;
tmp.newvalue = c2[k];
}
res.push(tmp);
}
}
for (k in c2) {
v = c2[k];
if (!((k.match(/^cfg(?:Num|Log|Author(?:IP)?|Date)$/)) || (c1[k] != null))) {
if (tr) {
tmp = {
title: $scope.translate(k),
id: k
};
} else {
tmp = {
title: k
};
}
if ((v != null) && typeof v === 'object') {
if (v.constructor === 'array') {
tmp.newvalue = v;
} else {
console.log("Iteration");
tmp.newnodes = toNodes(v, 'new');
}
} else {
tmp.newvalue = v;
}
res.push(tmp);
}
}
return res;
};
toNodes = function(c, s) {
var k, res, tmp, v;
res = [];
for (k in c) {
v = c[k];
tmp = {
title: k
};
if (typeof v === 'object') {
if (v.constructor === 'array') {
tmp[s + "value"] = v;
} else {
tmp[s + "nodes"] = toNodes(c[k], s);
}
} else {
tmp[s + "value"] = v;
}
res.push(tmp);
}
return res;
};
reverseTree = [];
buildTree = function(data) {
var elem, found, i, j, l, len, len1, len2, m, n, node, offset, path, res;
if (reverseTree == null) {
return data;
}
res = [];
for (j = 0, len = data.length; j < len; j++) {
elem = data[j];
offset = res;
path = reverseTree[elem.id] != null ? reverseTree[elem.id].split('/') : '';
for (l = 0, len1 = path.length; l < len1; l++) {
node = path[l];
if (node.length > 0) {
if (offset.length) {
found = -1;
for (i = m = 0, len2 = offset.length; m < len2; i = ++m) {
n = offset[i];
if (n.id === node) {
found = i;
}
}
if (found !== -1) {
offset = offset[found].nodes;
} else {
offset.push({
id: node,
title: $scope.translate(node),
nodes: []
});
offset = offset[offset.length - 1].nodes;
}
} else {
offset.push({
id: node,
title: $scope.translate(node),
nodes: []
});
offset = offset[0].nodes;
}
}
}
offset.push(elem);
}
return res;
};
$scope.newDiff = function() {
return $location.path("/" + $scope.cfg[0].cfgNum + "/" + $scope.cfg[1].cfgNum);
};
pathEvent = function(event, next, current) {
var n;
n = next.match(new RegExp('#!?/(latest|[0-9]+)(?:/(latest|[0-9]+))?$'));
if (n === null) {
$location.path('/latest');
} else {
$scope.waiting = true;
$q.all([
$translator.init($scope.lang), $http.get(staticPrefix + "reverseTree.json").then(function(response) {
reverseTree = response.data;
return console.log("Structure loaded");
}), getCfg(0, n[1]), n[2] != null ? getCfg(1, n[2]) : void 0
]).then(function() {
if (n[2] != null) {
return init();
} else {
if ($scope.cfg[0].prev) {
$scope.cfg[1] = $scope.cfg[0];
return getCfg(0, $scope.cfg[1].prev).then(function() {
return init();
});
} else {
$scope.data = [];
return $scope.waiting = false;
}
}
}, function() {
$scope.message = $scope.translate('error');
return $scope.waiting = false;
});
}
return true;
};
return $scope.$on('$locationChangeSuccess', pathEvent);
}
]);
}).call(this);

View File

@ -0,0 +1 @@
(function(){var llapp;llapp=angular.module("llngConfDiff",["ui.tree","ui.bootstrap","llApp","ngCookies"],function($rootScopeProvider){return $rootScopeProvider.digestTtl(15)});llapp.controller("DiffCtrl",["$scope","$http","$q","$translator","$location",function($scope,$http,$q,$translator,$location){var buildTree,getCfg,init,pathEvent,readDiff,reverseTree,toNodes;$scope.links=links;$scope.menulinks=menulinks;$scope.staticPrefix=staticPrefix;$scope.scriptname=scriptname;$scope.availableLanguages=availableLanguages;$scope.waiting=true;$scope.showM=false;$scope.cfg=[];$scope.data={};$scope.currentNode=null;$scope.translateTitle=function(node){return $translator.translateField(node,"title")};$scope.translateP=$translator.translateP;$scope.translate=$translator.translate;$scope.toggle=function(scope){return scope.toggle()};$scope.stoggle=function(scope,node){$scope.currentNode=node;return scope.toggle()};$scope.menuClick=function(button){if(button.popup){window.open(button.popup)}else{if(!button.action){button.action=button.title}switch(typeof button.action){case"function":button.action($scope.currentNode,$scope);break;case"string":$scope[button.action]();break;default:console.log(typeof button.action)}}return $scope.showM=false};$scope.getLanguage=function(lang){$scope.lang=lang;$scope.init();return $scope.showM=false};getCfg=function(b,n){var d;d=$q.defer();if($scope.cfg[b]==null||$scope.cfg[b]!==n){$http.get(""+confPrefix+n).then(function(response){var date;if(response&&response.data){$scope.cfg[b]=response.data;date=new Date(response.data.cfgDate*1e3);$scope.cfg[b].date=date.toLocaleString();console.log("Metadatas of cfg "+n+" loaded");return d.resolve("OK")}else{return d.reject(response)}},function(response){console.log(response);return d.reject("NOK")})}else{d.resolve()}return d.promise};init=function(){var d;$scope.message=null;$scope.currentNode=null;d=$q.defer();return $http.get(scriptname+"view/diff/"+$scope.cfg[0].cfgNum+"/"+$scope.cfg[1].cfgNum).then(function(response){var data;data=[];data=readDiff(response.data[0],response.data[1]);$scope.data=buildTree(data);$scope.message="";return $scope.waiting=false},function(response){return $scope.message=$scope.translate("error")+" : "+response.statusLine})};readDiff=function(c1,c2,tr){var k,res,tmp,v;if(tr==null){tr=true}res=[];for(k in c1){v=c1[k];if(tr){tmp={title:$scope.translate(k),id:k}}else{tmp={title:k}}if(!k.match(/^cfg(?:Num|Log|Author(?:IP)?|Date)$/)){if(v!=null&&typeof v==="object"){if(v.constructor==="array"){tmp.oldvalue=v;tmp.newvalue=c2[k]}else if(typeof c2[k]==="object"){tmp.nodes=readDiff(c1[k],c2[k],false)}else{tmp.oldnodes=toNodes(v,"old")}}else{tmp.oldvalue=v;tmp.newvalue=c2[k]}res.push(tmp)}}for(k in c2){v=c2[k];if(!(k.match(/^cfg(?:Num|Log|Author(?:IP)?|Date)$/)||c1[k]!=null)){if(tr){tmp={title:$scope.translate(k),id:k}}else{tmp={title:k}}if(v!=null&&typeof v==="object"){if(v.constructor==="array"){tmp.newvalue=v}else{console.log("Iteration");tmp.newnodes=toNodes(v,"new")}}else{tmp.newvalue=v}res.push(tmp)}}return res};toNodes=function(c,s){var k,res,tmp,v;res=[];for(k in c){v=c[k];tmp={title:k};if(typeof v==="object"){if(v.constructor==="array"){tmp[s+"value"]=v}else{tmp[s+"nodes"]=toNodes(c[k],s)}}else{tmp[s+"value"]=v}res.push(tmp)}return res};reverseTree=[];buildTree=function(data){var elem,found,i,j,l,len,len1,len2,m,n,node,offset,path,res;if(reverseTree==null){return data}res=[];for(j=0,len=data.length;j<len;j++){elem=data[j];offset=res;path=reverseTree[elem.id]!=null?reverseTree[elem.id].split("/"):"";for(l=0,len1=path.length;l<len1;l++){node=path[l];if(node.length>0){if(offset.length){found=-1;for(i=m=0,len2=offset.length;m<len2;i=++m){n=offset[i];if(n.id===node){found=i}}if(found!==-1){offset=offset[found].nodes}else{offset.push({id:node,title:$scope.translate(node),nodes:[]});offset=offset[offset.length-1].nodes}}else{offset.push({id:node,title:$scope.translate(node),nodes:[]});offset=offset[0].nodes}}}offset.push(elem)}return res};$scope.newDiff=function(){return $location.path("/"+$scope.cfg[0].cfgNum+"/"+$scope.cfg[1].cfgNum)};pathEvent=function(event,next,current){var n;n=next.match(new RegExp("#!?/(latest|[0-9]+)(?:/(latest|[0-9]+))?$"));if(n===null){$location.path("/latest")}else{$scope.waiting=true;$q.all([$translator.init($scope.lang),$http.get(staticPrefix+"reverseTree.json").then(function(response){reverseTree=response.data;return console.log("Structure loaded")}),getCfg(0,n[1]),n[2]!=null?getCfg(1,n[2]):void 0]).then(function(){if(n[2]!=null){return init()}else{if($scope.cfg[0].prev){$scope.cfg[1]=$scope.cfg[0];return getCfg(0,$scope.cfg[1].prev).then(function(){return init()})}else{$scope.data=[];return $scope.waiting=false}}},function(){$scope.message=$scope.translate("error");return $scope.waiting=false})}return true};return $scope.$on("$locationChangeSuccess",pathEvent)}])}).call(this);

View File

@ -33,7 +33,7 @@ This file contains:
$scope.waiting = true;
$scope.showM = false;
$scope.showT = false;
$scope.form = 'home';
$scope.form = 'homeViewer';
$scope.currentCfg = {};
$scope.viewPrefix = window.viewPrefix;
$scope.message = {};
@ -160,7 +160,7 @@ This file contains:
return $scope.showM = false;
};
$scope.home = function() {
$scope.form = 'home';
$scope.form = 'homeViewer';
return $scope.showM = false;
};
$scope.downloadConf = function() {
@ -528,7 +528,7 @@ This file contains:
};
$scope.showModal('message.html');
}
$scope.form = 'home';
$scope.form = 'homeViewer';
return $scope.waiting = false;
}, readError);
$scope.activeModule = "viewer";

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,121 @@
<TMPL_INCLUDE NAME="header.tpl">
<title>LemonLDAP::NG Manager</title>
<link rel="prefetch" href="<TMPL_VAR NAME="STATIC_PREFIX">forms/homeViewer.html" />
<link rel="prefetch" href="<TMPL_VAR NAME="STATIC_PREFIX">struct.json" />
</head>
<body ng-app="llngConfDiff" ng-controller="DiffCtrl" ng-csp>
<TMPL_INCLUDE NAME="menubar.tpl">
<div id="content" class="row container-fluid">
<div id="pleaseWait" ng-show="waiting"><span trspan="waitingForDatas"></span></div>
<!-- Tree -->
<aside id="left" class="col-lg-4 col-md-4 col-sm-5 col-xs-12 scrollable " ng-class="{'hidden-xs':!showT}" role="complementary">
<div class="panel panel-default">
<div class="panel-heading">
<p class="panel-title text-center" trspan="diffViewer"></p>
</div>
<div class="panel-body">
<div class="input-group input-group-sm">
<a ng-show="cfg[0].prev" class="input-group-addon link glyphicon glyphicon-arrow-left" href="#!/{{cfg[0].prev}}/{{cfg[1].prev}}" role="link"></a>
<span class="input-group-addon">1</span>
<input class="form-control" size="2" type="integer" ng-model="cfg[0].cfgNum"/>
<span class="input-group-addon">2</span>
<input class="form-control" size="2" type="integer" ng-model="cfg[1].cfgNum"/>
<span class="input-group-addon link glyphicon glyphicon-refresh" ng-click="newDiff()"></span>
<a ng-show="cfg[1].next" class="input-group-addon link glyphicon glyphicon-arrow-right" href="#!/{{cfg[0].next}}/{{cfg[1].next}}" role="link"></a>
</div>
</div>
</div>
<div class="region region-sidebar-first">
<section id="block-superfish-1" class="block block-superfish clearfix">
<div ui-tree data-drag-enabled="false" id="tree-root">
<div ng-show="data.length==0" class="center">
<span class="label label-warning" trspan="noDatas"></span>
</div>
<ol ui-tree-nodes="" ng-model="data">
<li ng-repeat="node in data" ui-tree-node ng-include="'nodes_renderer.html'" collapsed="true"></li>
</ol>
</div>
</section>
</div>
<div class="hresizer hidden-xs" resizer="vertical" resizer-left="#left" resizer-right="#right"></div>
</aside>
<!-- Right div -->
<div id="right" class="col-lg-8 col-md-8 col-sm-7 col-xs-12 scrollable" ng-class="{'hidden-xs':showT&&!showM}">
<h2 ng-if="message">{{message}}</h2>
<div class="panel panel-default" ng-if="currentNode">
<div class="panel-heading">
<h3 class="panel-title">{{currentNode.title}}</h3>
</div>
<table class="table table-striped">
<tr ng-show="currentNode.oldvalue">
<th><span class="old" trspan="oldValue"></span></th>
<td id="tdoldarray" ng-show="currentNode.oldvalue.constructor === 'array'">{{currentNode.oldvalue|json}}</td>
<td id="tdold" ng-hide="currentNode.oldvalue.constructor === 'array'">{{currentNode.oldvalue}}</td>
</tr>
<tr ng-show="currentNode.newvalue">
<th><span class="new" trspan="newValue"></span></th>
<td id="tdnewarray" ng-show="currentNode.newvalue.constructor === 'array'">{{currentNode.newvalue|json}}</td>
<td id="tdnew" ng-hide="currentNode.newvalue.constructor === 'array'">{{currentNode.newvalue}}</td>
</tr>
</table>
</div>
</div>
</div>
<script type="text/ng-template" id="nodes_renderer.html">
<div ui-tree-handle class="tree-node tree-node-content panel-info tree-node-default">
<span ng-include="'arrow.html'"></span>
<span id="t-{{node.id}}" ng-click="stoggle(this,node)">{{node.title}}</span>
</div>
<ol ui-tree-nodes="" ng-model="node" ng-class="{hidden: collapsed}" ng-include="'subnodes.html'">
</ol>
</script>
<script type="text/ng-template" id="newnodes_renderer.html">
<div ui-tree-handle class="tree-node tree-node-content panel-info tree-node-default">
<span ng-include="'arrow.html'"></span>
<span id="t-{{node.id}}" ng-click="stoggle(this,node)" class="new">{{node.title}}</span>
</div>
<ol ui-tree-nodes="" ng-model="node" ng-class="{hidden: collapsed}" ng-include="'subnodes.html'">
</ol>
</script>
<script type="text/ng-template" id="oldnodes_renderer.html">
<div ui-tree-handle class="tree-node tree-node-content panel-info tree-node-default">
<span ng-include="'arrow.html'"></span>
<span id="t-{{node.id}}" ng-click="stoggle(this,node)" class="old">{{node.title}}</span>
</div>
<ol ui-tree-nodes="" ng-model="node" ng-class="{hidden: collapsed}" ng-include="'subnodes.html'">
</ol>
</script>
<script type="text/ng-template" id="arrow.html">
<a class="btn btn-node btn-sm" ng-click="toggle(this)" ng-if="node.nodes||node.newnodes||node.oldnodes">
<span class="glyphicon" ng-class="{'glyphicon-chevron-right': collapsed,'glyphicon-chevron-down': !collapsed}"></span>
</a>
<a class="btn btn-node btn-sm" ng-click="toggle(this)" ng-if="node.newvalue||node.oldvalue||node.value">
<span class="glyphicon glyphicon-eye-open"></span>
</a>
</script>
<script type="text/ng-template" id="subnodes.html">
<li ng-repeat="node in node.nodes" ui-tree-node ng-include="'nodes_renderer.html'" collapsed="true"></li>
<li ng-repeat="node in node.newnodes" ui-tree-node ng-include="'newnodes_renderer.html'" collapsed="true"></li>
<li ng-repeat="node in node.oldnodes" ui-tree-node ng-include="'oldnodes_renderer.html'" collapsed="true"></li>
</script>
<TMPL_INCLUDE NAME="scripts.tpl">
<!-- //if:jsminified
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">js/conftree.min.js"></script>
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">js/veiwDiff.min.js"></script>
//else -->
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">js/conftree.js"></script>
<script type="text/javascript" src="<TMPL_VAR NAME="STATIC_PREFIX">js/viewDiff.js"></script>
<!-- //endif -->
<TMPL_INCLUDE NAME="footer.tpl">

View File

@ -50,7 +50,9 @@
</ul>
</div>
</div>
<form class="form-group slide-animate-container" ng-include="formPrefix+form+'.html'" scope="$scope" />
<TMPL_IF NAME="ALLOWDIFF">
<form class="form-group slide-animate-container" ng-include="formPrefix+form+'.html'" scope="$scope" />
</TMPL_IF>
</div>
<!-- Help container -->
<div id="bottom" ng-if="showH" class="hidden-xs">

View File

@ -48,7 +48,7 @@ my @notManagedAttributes = (
'log4perlConfFile', 'userSyslogFacility', 'logger', 'sentryDsn',
'syslogFacility', 'userLogger', 'logLevel',
# Viewer
'viewerHiddenPK', 'viewerAllowBrowser',
'viewerHiddenPK', 'viewerAllowBrowser', 'viewerAllowDiff',
# Other ini-only prms
'configStorage', 'status', 'localStorageOptions', 'localStorage',