diff --git a/e2e-tests/lemonldap-ng.ini b/e2e-tests/lemonldap-ng.ini index 3b023245c..aa49c3de6 100644 --- a/e2e-tests/lemonldap-ng.ini +++ b/e2e-tests/lemonldap-ng.ini @@ -40,8 +40,9 @@ useRedirectOnError = 0 enabledModules = conf, sessions, notifications, 2ndFA, viewer protection = manager -viewerHiddenPK = samlIDPMetaDataNodes samlSPMetaDataNodes portalDisplayLogout +viewerHiddenKeys = samlIDPMetaDataNodes samlSPMetaDataNodes portalDisplayLogout captcha_login_enabled viewerAllowBrowser = 1 +viewerAllowDiff = 1 staticPrefix = /static languages = fr, en, vi, ar, de, it, zh diff --git a/lemonldap-ng-common/lemonldap-ng.ini b/lemonldap-ng-common/lemonldap-ng.ini index 83befbba3..b7a384718 100644 --- a/lemonldap-ng-common/lemonldap-ng.ini +++ b/lemonldap-ng-common/lemonldap-ng.ini @@ -362,8 +362,9 @@ languages = fr, en, vi, ar enabledModules = conf, sessions, notifications, 2ndFA, viewer ; Viewer options - Default values -;viewerHiddenPK = samlIDPMetaDataNodes samlSPMetaDataNodes +;viewerHiddenKeys = samlIDPMetaDataNodes samlSPMetaDataNodes ;viewerAllowBrowser = 0 +;viewerAllowDiff = 0 ;[node-handler] ; diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/DefaultValues.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/DefaultValues.pm index ef0d11438..d0e6bb8a5 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/DefaultValues.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/Conf/DefaultValues.pm @@ -296,7 +296,7 @@ sub defaultValues { 'useRedirectOnError' => 1, 'useSafeJail' => 1, 'utotp2fActivation' => 0, - 'viewerHiddenPK' => 'samlIDPMetaDataNodes samlSPMetaDataNodes', + 'viewerHiddenKeys' => 'samlIDPMetaDataNodes samlSPMetaDataNodes', 'webIDAuthnLevel' => 1, 'webIDExportedVars' => {}, 'whatToTrace' => 'uid', diff --git a/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI.pm b/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI.pm index d90face37..04e00befc 100644 --- a/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI.pm +++ b/lemonldap-ng-common/lib/Lemonldap/NG/Common/PSGI.pm @@ -174,7 +174,7 @@ body{background:#000;color:#fff;padding:10px 50px;font-family:sans-serif;}a{text

$title

$err

-
LemonLDAP::NG
' +
LemonLDAP::NG
"; return [ diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Init.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Init.pm index e188458e9..d2af0e1e8 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Init.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Main/Init.pm @@ -58,7 +58,7 @@ sub logLevelInit { $logger = $class->localConfig->{userLogger} || $logger; eval "require $logger"; die $@ if ($@); - $class->userLogger( $logger->new( $class->localConfig ), user => 1 ); + $class->userLogger( $logger->new( $class->localConfig, user => 1 ) ); $class->logger->debug("User logger $logger loaded"); } diff --git a/lemonldap-ng-manager/MANIFEST b/lemonldap-ng-manager/MANIFEST index 35f38bd52..55b75b6c4 100644 --- a/lemonldap-ng-manager/MANIFEST +++ b/lemonldap-ng-manager/MANIFEST @@ -37,6 +37,7 @@ site/coffee/llApp.coffee site/coffee/manager.coffee site/coffee/notifications.coffee site/coffee/sessions.coffee +site/coffee/viewDiff.coffee site/coffee/viewer.coffee site/htdocs/manager.fcgi site/htdocs/manager.psgi @@ -105,6 +106,7 @@ site/htdocs/static/forms/file.html site/htdocs/static/forms/grant.html site/htdocs/static/forms/grantContainer.html site/htdocs/static/forms/home.html +site/htdocs/static/forms/homeViewer.html site/htdocs/static/forms/int.html site/htdocs/static/forms/keyText.html site/htdocs/static/forms/keyTextContainer.html @@ -158,6 +160,8 @@ site/htdocs/static/js/notifications.js site/htdocs/static/js/notifications.min.js site/htdocs/static/js/sessions.js site/htdocs/static/js/sessions.min.js +site/htdocs/static/js/viewDiff.js +site/htdocs/static/js/viewDiff.min.js site/htdocs/static/js/viewer.js site/htdocs/static/js/viewer.min.js site/htdocs/static/languages/ar.json @@ -191,6 +195,7 @@ site/templates/notifications.tpl site/templates/scripts.tpl site/templates/sessions.tpl site/templates/tree.tpl +site/templates/viewDiff.tpl site/templates/viewer.tpl t/02-HTML-template.t t/03-HTML-forms.t @@ -208,6 +213,7 @@ t/50-notifications-DBI.t t/50-notifications.t t/60-2ndfa.t t/70-viewer.t +t/71-viewer-with-no-diff.t t/80-attributes.t t/90-translations.t t/99-pod.t @@ -218,7 +224,8 @@ t/jsonfiles/11-modified-with-confirmation.json t/jsonfiles/12-modified.json t/jsonfiles/14-bad.json t/jsonfiles/15-combination.json +t/jsonfiles/70-diff.json t/lemonldap-ng-dbi.ini -t/lemonldap-ng-noBrowser.ini +t/lemonldap-ng-noDiff.ini t/lemonldap-ng.ini t/test-lib.pm diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager.pm index d079bffed..60500c5e2 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager.pm @@ -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 @@ -142,6 +144,7 @@ sub javascript { my ($self) = @_; return 'var formPrefix=staticPrefix+"forms/";var confPrefix=scriptname+"confs/";var viewPrefix=scriptname+"view/";' + . 'var allowDiff=' . $self->dif . ';' . ( $self->links ? 'var links=' . to_json( $self->links ) . ';' : '' ) . ( $self->menuLinks diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm index 24e16d8bf..a7a20f634 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Attributes.pm @@ -3566,7 +3566,11 @@ qr/^(?:(?:(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a- 'default' => 0, 'type' => 'bool' }, - 'viewerHiddenPK' => { + 'viewerAllowDiff' => { + 'default' => 0, + 'type' => 'bool' + }, + 'viewerHiddenKeys' => { 'default' => 'samlIDPMetaDataNodes samlSPMetaDataNodes', 'type' => 'text' }, diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm index 8bdf42742..5a53b3e83 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Build/Attributes.pm @@ -916,10 +916,10 @@ sub attributes { }, # Viewer - viewerHiddenPK => { + viewerHiddenKeys => { type => 'text', default => 'samlIDPMetaDataNodes samlSPMetaDataNodes', - documentation => 'ConfTree hidden primary keys', + documentation => 'Hidden Conf keys', flags => 'm', }, viewerAllowBrowser => { @@ -927,6 +927,11 @@ sub attributes { default => 0, documentation => 'Allow configuration browser', }, + viewerAllowDiff => { + type => 'bool', + default => 0, + documentation => 'Allow configuration diff', + }, # Notification oldNotifFormat => { diff --git a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Viewer.pm b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Viewer.pm index fe934e927..62412ce25 100644 --- a/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Viewer.pm +++ b/lemonldap-ng-manager/lib/Lemonldap/NG/Manager/Viewer.pm @@ -26,7 +26,7 @@ sub addRoutes { $self->ua( Lemonldap::NG::Common::UserAgent->new($conf) ); my $hiddenPK = ''; - $hiddenPK = $self->{viewerHiddenPK} || $conf->{viewerHiddenPK}; + $hiddenPK = $self->{viewerHiddenKeys} || $conf->{viewerHiddenKeys}; my @enabledPK = (); my @keys = qw(virtualHosts samlIDPMetaDataNodes samlSPMetaDataNodes applicationList oidcOPMetaDataNodes oidcRPMetaDataNodes @@ -66,12 +66,15 @@ sub addRoutes { ); } - # Other keys - $self->addRoute( view => { ':cfgNum' => { '*' => 'getKey' } }, ['GET'] ) + # Difference between confs + if ( $self->{viewerAllowDiff} || $conf->{viewerAllowDiff} ) { + $self->addRoute( + view => { diff => { ':conf1' => { ':conf2' => 'viewDiff' } } } ) + ->addRoute( 'viewDiff.html', undef, ['GET'] ); + } - # Difference between confs - ->addRoute( diff => { ':conf1' => { ':conf2' => 'diff' } } ) - ->addRoute( 'diff.html', undef, ['GET'] ); + # Other keys + $self->addRoute( view => { ':cfgNum' => { '*' => 'getKey' } }, ['GET'] ); } sub getConfByNum { @@ -79,9 +82,43 @@ 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->{viewerHiddenKeys} || ''; + $self->logger->debug("Deleting hidden Conf keys..."); + foreach ( split /\s+/, $hiddenKeys ) { + $self->logger->debug("-> Delete $_"); + delete $res[0]->{$_}; + delete $res[1]->{$_}; + } + return $self->sendJSONresponse( $req, [@res] ); } sub rejectKey { diff --git a/lemonldap-ng-manager/site/coffee/viewDiff.coffee b/lemonldap-ng-manager/site/coffee/viewDiff.coffee new file mode 100644 index 000000000..ce9272164 --- /dev/null +++ b/lemonldap-ng-manager/site/coffee/viewDiff.coffee @@ -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 +] diff --git a/lemonldap-ng-manager/site/coffee/viewer.coffee b/lemonldap-ng-manager/site/coffee/viewer.coffee index 2fb0a0fb2..6d387578e 100644 --- a/lemonldap-ng-manager/site/coffee/viewer.coffee +++ b/lemonldap-ng-manager/site/coffee/viewer.coffee @@ -27,9 +27,10 @@ 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.allowDiff = window.allowDiff $scope.message = {} $scope.result = '' @@ -133,7 +134,7 @@ llapp.controller 'TreeCtrl', [ # Display main form $scope.home = -> - $scope.form = 'home' + $scope.form = 'homeViewer' $scope.showM = false # Download raw conf @@ -438,7 +439,7 @@ llapp.controller 'TreeCtrl', [ title: 'emptyConf' message: '__zeroConfExplanations__' $scope.showModal 'message.html' - $scope.form = 'home' + $scope.form = 'homeViewer' $scope.waiting = false , readError # Colorized link diff --git a/lemonldap-ng-manager/site/htdocs/static/forms/homeViewer.html b/lemonldap-ng-manager/site/htdocs/static/forms/homeViewer.html new file mode 100644 index 000000000..b215e9afa --- /dev/null +++ b/lemonldap-ng-manager/site/htdocs/static/forms/homeViewer.html @@ -0,0 +1,49 @@ +
+
+

+ + + + () + +

+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ {{currentCfg.cfgNum}} +
{{currentCfg.cfgAuthor}}
{{currentCfg.cfgAuthorIP}}
{{currentCfg.date}}
{{currentCfg.cfgVersion}}
{{currentCfg.cfgLog}}
+
+ diff --git a/lemonldap-ng-manager/site/htdocs/static/js/viewDiff.js b/lemonldap-ng-manager/site/htdocs/static/js/viewDiff.js new file mode 100644 index 000000000..90779a200 --- /dev/null +++ b/lemonldap-ng-manager/site/htdocs/static/js/viewDiff.js @@ -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); diff --git a/lemonldap-ng-manager/site/htdocs/static/js/viewDiff.min.js b/lemonldap-ng-manager/site/htdocs/static/js/viewDiff.min.js new file mode 100644 index 000000000..7aeec6a66 --- /dev/null +++ b/lemonldap-ng-manager/site/htdocs/static/js/viewDiff.min.js @@ -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;j0){if(offset.length){found=-1;for(i=m=0,len2=offset.length;m0){$scope.message={title:"badRequest",message:j,items:[]}}else{$scope.message={title:"networkProblem",message:"",items:[]}}return $scope.showModal("message.html")};$scope.showModal=function(tpl,init){var d,modalInstance;modalInstance=$uibModal.open({templateUrl:tpl,controller:"ModalInstanceCtrl",size:"lg",resolve:{elem:function(){return function(s){return $scope[s]}},set:function(){return function(f,s){return $scope[f]=s}},init:function(){return init}}});d=$q.defer();modalInstance.result.then(function(msgok){$scope.message={title:"",message:"",items:[]};return d.resolve(msgok)},function(msgnok){$scope.message={title:"",message:"",items:[]};return d.reject(msgnok)});return d.promise};$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.home=function(){$scope.form="home";return $scope.showM=false};$scope.downloadConf=function(){return window.open($scope.viewPrefix+$scope.currentCfg.cfgNum+"?full=1")};id=1;$scope._findContainer=function(){return $scope._findScopeContainer().$modelValue};$scope._findScopeContainer=function(){var cs;cs=$scope.currentScope;while(!cs.$modelValue.type.match(/Container$/)){cs=cs.$parentNodeScope}return cs};$scope._findScopeByKey=function(k){var cs;cs=$scope.currentScope;while(!(cs.$modelValue.title===k)){cs=cs.$parentNodeScope}return cs};_getAll=function(node){var d,d2;d=$q.defer();d2=$q.defer();if(node._nodes){_stoggle(node);d.resolve()}else if(node.cnodes){_download(node).then(function(){return d.resolve()})}else if(node.nodes||node.data){d.resolve()}else{$scope.getKey(node).then(function(){return d.resolve()})}d.promise.then(function(){var l,len,n,ref,t;t=[];if(node.nodes){ref=node.nodes;for(l=0,len=ref.length;l0){tmp=p.nodes[ind];p.nodes[ind]=p.nodes[ind-1];p.nodes[ind-1]=tmp}return ind};$scope.inSelect=function(value){var l,len,n,ref;ref=$scope.currentNode.select;for(l=0,len=ref.length;l0?node.comment:node.re};$scope.filters={};$scope.execFilters=function(scope){var filter,func,ref;scope=scope?scope:$scope;ref=$scope.filters;for(filter in ref){func=ref[filter];if($scope.filters.hasOwnProperty(filter)){return window.filterFunctions[filter](scope,$q,func)}}return false};$scope.stoggle=function(scope){var node;node=scope.$modelValue;_stoggle(node);return scope.toggle()};_stoggle=function(node){var a,l,len,len1,len2,m,n,o,ref,ref1,ref2;ref=["nodes","nodes_cond"];for(l=0,len=ref.length;l0){$scope.message={title:"badRequest",message:j,items:[]}}else{$scope.message={title:"networkProblem",message:"",items:[]}}return $scope.showModal("message.html")};$scope.showModal=function(tpl,init){var d,modalInstance;modalInstance=$uibModal.open({templateUrl:tpl,controller:"ModalInstanceCtrl",size:"lg",resolve:{elem:function(){return function(s){return $scope[s]}},set:function(){return function(f,s){return $scope[f]=s}},init:function(){return init}}});d=$q.defer();modalInstance.result.then(function(msgok){$scope.message={title:"",message:"",items:[]};return d.resolve(msgok)},function(msgnok){$scope.message={title:"",message:"",items:[]};return d.reject(msgnok)});return d.promise};$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.home=function(){$scope.form="homeViewer";return $scope.showM=false};$scope.downloadConf=function(){return window.open($scope.viewPrefix+$scope.currentCfg.cfgNum+"?full=1")};id=1;$scope._findContainer=function(){return $scope._findScopeContainer().$modelValue};$scope._findScopeContainer=function(){var cs;cs=$scope.currentScope;while(!cs.$modelValue.type.match(/Container$/)){cs=cs.$parentNodeScope}return cs};$scope._findScopeByKey=function(k){var cs;cs=$scope.currentScope;while(!(cs.$modelValue.title===k)){cs=cs.$parentNodeScope}return cs};_getAll=function(node){var d,d2;d=$q.defer();d2=$q.defer();if(node._nodes){_stoggle(node);d.resolve()}else if(node.cnodes){_download(node).then(function(){return d.resolve()})}else if(node.nodes||node.data){d.resolve()}else{$scope.getKey(node).then(function(){return d.resolve()})}d.promise.then(function(){var l,len,n,ref,t;t=[];if(node.nodes){ref=node.nodes;for(l=0,len=ref.length;l0){tmp=p.nodes[ind];p.nodes[ind]=p.nodes[ind-1];p.nodes[ind-1]=tmp}return ind};$scope.inSelect=function(value){var l,len,n,ref;ref=$scope.currentNode.select;for(l=0,len=ref.length;l0?node.comment:node.re};$scope.filters={};$scope.execFilters=function(scope){var filter,func,ref;scope=scope?scope:$scope;ref=$scope.filters;for(filter in ref){func=ref[filter];if($scope.filters.hasOwnProperty(filter)){return window.filterFunctions[filter](scope,$q,func)}}return false};$scope.stoggle=function(scope){var node;node=scope.$modelValue;_stoggle(node);return scope.toggle()};_stoggle=function(node){var a,l,len,len1,len2,m,n,o,ref,ref1,ref2;ref=["nodes","nodes_cond"];for(l=0,len=ref.length;l + + LemonLDAP::NG Manager + forms/homeViewer.html" /> + struct.json" /> + + + + + + +
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + diff --git a/lemonldap-ng-manager/site/templates/viewer.tpl b/lemonldap-ng-manager/site/templates/viewer.tpl index 65835bb76..a48797033 100644 --- a/lemonldap-ng-manager/site/templates/viewer.tpl +++ b/lemonldap-ng-manager/site/templates/viewer.tpl @@ -50,7 +50,7 @@ -
+