## @file
# Session explorer
## @class
# Session explorer.
package Lemonldap::NG::Manager::Sessions;
use strict;
use Lemonldap::NG::Handler::CGI qw(:globalStorage :locationRules);
use Lemonldap::NG::Common::Apache::Session; #inherits
use Lemonldap::NG::Common::Conf; #link protected conf Configuration
use Lemonldap::NG::Common::Conf::Constants; #inherits
#inherits Apache::Session
our $whatToTrace;
*whatToTrace = \$Lemonldap::NG::Handler::_CGI::whatToTrace;
our $VERSION = '0.11';
use base qw(Lemonldap::NG::Handler::CGI);
## @cmethod Lemonldap::NG::Manager::Sessions new(hashRef args)
# Constructor.
# @param $args Arguments for Lemonldap::NG::Handler::CGI::new(). Must contains
# 3 keys for Lemonldap::NG::Manager::Sessions:
# - jqueryUri HTTP path to jquery.js
# - personnalCss Optional HTTP path to custom CSS file
# - imagePath HTTP path to the images directory
# @return New Lemonldap::NG::Manager::Sessions object
sub new {
my ( $class, $args ) = @_;
my $self = $class->SUPER::new($args)
or $class->abort( 'Unable to start ' . __PACKAGE__,
'See Apache logs for more' );
# Try to get configuration values from local configuration
my $config = Lemonldap::NG::Common::Conf->new( $self->{configStorage} );
unless ($config) {
$self->abort( "Unable to start",
"Configuration not loaded\n" . $Lemonldap::NG::Common::Conf::msg );
}
my $localconf = $config->getLocalConf(MANAGERSECTION);
if ($localconf) {
$self->{$_} = $args->{$_} || $localconf->{$_}
foreach ( keys %$localconf );
}
# Check some paramaters
foreach (qw(jqueryUri personnalCss imagePath)) {
$self->{$_}
or $self->lmLog( "$_ is not set, falling to default value", 'debug' );
}
eval "use $globalStorage";
$class->abort( "Unable to load $globalStorage", $@ ) if ($@);
$self->{managerSkin} = "default" unless defined $self->{managerSkin};
return $self;
}
## @method void process()
# Main method.
sub process {
my $self = shift;
if ( $ENV{PATH_INFO} eq "/css" ) {
print $self->header_public( $ENV{SCRIPT_FILENAME}, -type => 'text/css',
);
$self->css;
exit;
}
elsif ( $ENV{PATH_INFO} eq "/js" ) {
print $self->header_public( $ENV{SCRIPT_FILENAME},
-type => 'text/javascript', );
$self->js;
exit;
}
# Check if we use X-FORWARDED-FOR header for IP
my $ipField = $self->{useXForwardedForIP} ? "xForwardedForAddr" : "ipAddr";
# Beginning of the job
# User connected from more than 1 IP
if ( $self->param('doubleIp') ) {
my ( $byUid, $byIp );
$globalStorage->get_key_from_all_sessions(
$globalStorageOptions,
sub {
my $entry = shift;
my $id = shift;
next if ( $entry->{_httpSessionType} );
push @{ $byUid->{ $entry->{$whatToTrace} }
->{ $entry->{$ipField} } },
{ id => $id, _utime => $entry->{_utime} };
undef;
}
);
$self->start('Sessions multi-IP');
$self->window("Sessions multi-IP");
foreach my $uid (
sort { ( keys %{ $byUid->{$b} } ) <=> ( keys %{ $byUid->{$a} } ) }
keys %$byUid
)
{
last if ( ( keys %{ $byUid->{$uid} } ) == 1 );
print "
$uid ";
foreach my $ip ( sort keys %{ $byUid->{$uid} } ) {
print "$ip ";
foreach my $session ( sort { $a->{_utime} <=> $b->{_utime} }
@{ $byUid->{$uid}->{$ip} } )
{
print
"{id}\">{id}');\">"
. localtime( $session->{_utime} )
. " ";
}
print " ";
}
print " ";
}
$self->end();
}
# Request for IP addresses
elsif ( my $req = $self->param('fullip') ) {
my $byUid;
my $reip = quotemeta($req);
$reip =~ s/\\\*/\.\*/g;
$globalStorage->get_key_from_all_sessions(
$globalStorageOptions,
sub {
my $entry = shift;
my $id = shift;
next if ( $entry->{_httpSessionType} );
if ( $entry->{$ipField} eq $reip ) {
push @{ $byUid->{ $entry->{$ipField} }
->{ $entry->{$whatToTrace} } },
{ id => $id, _utime => $entry->{_utime} };
}
undef;
}
);
$self->start("IP : $req");
$self->window("$req");
foreach my $ip ( sort keys %$byUid ) {
print "$ip ";
foreach my $uid ( sort keys %{ $byUid->{$ip} } ) {
$self->ajaxNode(
$uid,
$uid
. (
@{ $byUid->{$ip}->{$uid} } > 1
? " ("
. @{ $byUid->{$ip}->{$uid} }
. " sessions) "
: ''
),
"uid=$uid"
);
}
print " ";
}
$self->end();
}
# Request for users
elsif ( my $req = $self->param('fulluid') ) {
my $byUid;
my $reuser = quotemeta($req);
$reuser =~ s/\\\*/\.\*/g;
$globalStorage->get_key_from_all_sessions(
$globalStorageOptions,
sub {
my $entry = shift;
my $id = shift;
next if ( $entry->{_httpSessionType} );
if ( $entry->{$whatToTrace} =~ /^$reuser$/ ) {
push @{ $byUid->{ $entry->{$whatToTrace} } },
{ id => $id, _utime => $entry->{_utime} };
}
undef;
}
);
$self->start("Users : $req");
$self->window("$req");
foreach my $uid ( sort keys %$byUid ) {
$self->ajaxNode(
$uid,
$uid
. (
@{ $byUid->{$uid} } > 1
? " ("
. @{ $byUid->{$uid} }
. " sessions) "
: ''
),
"uid=$uid"
);
}
$self->end();
}
# Ajax request to delete a session
elsif ( my $id = $self->param('delete') ) {
my %h;
print $self->header( -type => 'text/html; charset=utf8' );
eval { tie %h, $globalStorage, $id, $globalStorageOptions; };
if ($@) {
print "Error : $@ \n";
}
else {
my $uid = $h{uid};
if ( $h{_httpSession} ) {
my %h2;
eval {
tie %h2, $globalStorage, $h{_httpSession},
$globalStorageOptions;
tied(%h2)->delete();
};
if ($@) {
print "Error : $@ ";
}
}
eval { tied(%h)->delete(); };
if ($@) {
print "Error : $@ ";
}
else {
print "Session effacée ($uid) ";
}
}
}
# Ajax request to dump a session
elsif ( my $id = ( $self->param('session') || $self->param('id') ) ) {
my %h;
print $self->header( -type => 'text/html; charset=utf8' );
eval { tie %h, $globalStorage, $id, $globalStorageOptions; };
if ($@) {
print "Error : $@ \n";
}
else {
print
"Session démarrée le "
. localtime( $h{_utime} )
. '
Membre des groupes SSO :
';
print "$_ " foreach ( sort split /\s+/, $h{groups} );
print ' ';
print
'Attributs et macros :
';
foreach my $attr ( sort keys %h ) {
next if ( $attr =~ /^(?:_utime|groups)$/ );
# Hide password value
if ( $attr =~ /^_password$/ ) { $h{$attr} = "******"; }
print ''
. htmlquote($attr)
. ' : '
. htmlquote( $h{$attr} )
. ' '
if ( $h{$attr} );
}
print '
';
untie %h;
}
}
# Ajax request to see users by IP
elsif ( my $ip = $self->param('uidByIp') ) {
my $byUser;
print $self->header( -type => 'text/html; charset=utf8' );
$globalStorage->get_key_from_all_sessions(
$globalStorageOptions,
sub {
my $entry = shift;
my $id = shift;
next if ( $entry->{_httpSessionType} );
if ( $entry->{$ipField} eq $ip ) {
push @{ $byUser->{ $entry->{$whatToTrace} } },
{ id => $id, _utime => $entry->{_utime} };
}
undef;
}
);
foreach my $user ( sort keys %$byUser ) {
print "$user ";
foreach my $session ( sort { $a->{_utime} <=> $b->{_utime} }
@{ $byUser->{$user} } )
{
print
"{id}\">{id}');\">"
. localtime( $session->{_utime} )
. " ";
}
print " ";
}
}
# Ajax request to see connexions from a user
elsif ( my $uid = $self->param('uid') ) {
my $byIp;
print $self->header( -type => 'text/html; charset=utf8' );
$globalStorage->get_key_from_all_sessions(
$globalStorageOptions,
sub {
my $entry = shift;
my $id = shift;
next if ( $entry->{_httpSessionType} );
if ( $entry->{$whatToTrace} eq $uid ) {
push @{ $byIp->{ $entry->{$ipField} } },
{ id => $id, _utime => $entry->{_utime} };
}
undef;
}
);
foreach my $ip ( sort keys %$byIp ) {
print "$ip ";
foreach my $session ( sort { $a->{_utime} <=> $b->{_utime} }
@{ $byIp->{$ip} } )
{
print
"{id}\">{id}');\">"
. localtime( $session->{_utime} )
. " ";
}
print " ";
}
}
# Ajax request to list users starting by a letter
elsif ( defined( $self->param('letter') ) ) {
my $letter = $self->param('letter');
my ($byUid);
print $self->header( -type => 'text/html; charset=utf8' );
$globalStorage->get_key_from_all_sessions(
$globalStorageOptions,
sub {
my $entry = shift;
next if ( $entry->{_httpSessionType} );
$entry->{$whatToTrace} =~ /^$letter/ or return undef;
$byUid->{ $entry->{$whatToTrace} }++;
},
);
foreach my $uid ( sort keys %$byUid ) {
$self->ajaxNode(
$uid,
$uid
. (
$byUid->{$uid} > 1
? " ($byUid->{$uid} sessions) "
: ''
),
"uid=$uid"
);
}
}
# Display by IP classes
elsif ( $self->param('ipclasses') ) {
my $partial = $self->param('p') ? $self->param('p') . '.' : '';
my $repartial = quotemeta($partial);
my ( $byIp, $count );
$globalStorage->get_key_from_all_sessions(
$globalStorageOptions,
sub {
my $entry = shift;
next if ( $entry->{_httpSessionType} );
$entry->{$ipField} =~ /^$repartial(\d+)/ or return undef;
$byIp->{$1}++;
$count++;
undef;
}
);
# Ajax request to list ip subclasses
if ($partial) {
print $self->header( -type => 'text/html; charset=utf8' );
}
# Display by IP subclass
else {
$self->start("Active sessions ($count)");
$self->window(
"Sessions par réseaux ($count) ");
}
foreach my $ip ( sort { $a <=> $b } keys %$byIp ) {
$self->ajaxNode(
"$partial$ip",
"$partial$ip ($byIp->{$ip}) ",
(
$partial !~ /^\d+\.\d+\.\d+/
? "ipclasses=1&p=$partial$ip"
: "uidByIp=$partial$ip"
)
);
}
$self->end() unless ($partial);
}
# Default display
else {
my ( $byUid, $count );
$globalStorage->get_key_from_all_sessions(
$globalStorageOptions,
sub {
my $entry = shift;
next if ( $entry->{_httpSessionType} );
$entry->{$whatToTrace} =~ /^(\w)/ or return undef;
$byUid->{$1}++;
$count++;
undef;
}
);
$self->start("Active sessions ($count)");
$self->window("Sessions ($count) ");
foreach my $letter ( sort keys %$byUid ) {
$self->ajaxNode( "li_$letter",
"$letter ($byUid->{$letter} sessions) ",
"letter=$letter" );
}
$self->end();
}
}
## @fn protected string htmlquote(string s)
# Change <, > and & to HTML encoded values in the string
# @param $s HTML string
# @return HTML string
sub htmlquote {
my $s = shift;
$s =~ s/</g;
$s =~ s/>/>/g;
$s =~ s/&/&/g;
return $s;
}
## @method protected void start()
# Display HTTP and HTML headers.
sub start {
my $self = shift;
print $self->header( -type => 'text/html; charset=utf8', );
print $self->start_html(
-title => shift || 'Sessions Lemonldap::NG',
-encoding => 'utf8',
-script => [
{
-language => 'JavaScript1.2',
-src => $self->{jqueryUri} || 'jquery.js',
},
{
-language => 'JavaScript1.2',
-src => "$ENV{SCRIPT_NAME}/js",
},
{
-language => 'JavaScript1.2',
-code => '$(document).ready(function(){
var simpleTreeCollection
simpleTreeCollection = $(".simpleTree").simpleTree({
autoclose: true,
drag: false,
afterClick:function(node){
//alert("text-"+$("span:first",node).text());
},
afterDblClick:function(node){
//alert("text-"+$("span:first",node).text());
},
afterMove:function(destination, source, pos){
//alert("destination-"+$("span:first",destination).text()+" source-"+$("span:first",source).text()+" pos-"+pos);
},
afterAjax:function() {
//alert("Loaded");
},
animate:true
//,docToFolderConvert:true
});
});
function del(session) {
$("#content").load("' . $ENV{SCRIPT_NAME} . '?delete="+session);
}
function display(session) {
$("#content").load("'
. $ENV{SCRIPT_NAME} . '?session="+session);
}
',
},
],
-style => {
-src => [
"$ENV{SCRIPT_NAME}/css",
"skins/" . $self->{managerSkin} . "/jquery-ui-1.7.2.custom.css",
( $self->{personnalCss} ? $self->{personnalCss} : () )
],
},
);
print '
';
}
## @method protected void ajaxnode(string id, string text, string param)
# Display tree node with Ajax functions inside for opening the node.
# @param $id HTML id of the element.
# @param $text text to display
# @param $param Parameters for the Ajax query
sub ajaxNode {
my ( $self, $id, $text, $param ) = @_;
print
"$text \n{url:$ENV{SCRIPT_NAME}?$param} \n";
}
## @method protected void window(string root)
# Design the main window
# @param $root Text to display in the root node of the tree
sub window {
my $self = shift;
my $root = shift;
print '
' . $root
. ' ';
}
## @method protected void end()
# Display the end of HTML page.
sub end {
my $self = shift;
print
'
';
print $self->end_html();
}
1;
## @method protected css()
# Display the main CSS file (called by http://manager.example.com/sessions.pl/css)
sub css {
my $self = shift;
print <<"EOF";
body
{
font: normal 12px arial, tahoma, helvetica, sans-serif;
margin:0;
}
#header
{
background-color:#000000;
padding:14px;
height:36px;
width:auto;
}
#header img, #header p
{
float:left;
}
#header p
{
margin:5px 10px;
padding:5px;
}
.simpleTree
{
overflow:auto;
margin:20px;
margin-top:0px;
margin-bottom:0px;
/*
* width: 250px;
* height:350px;
* overflow:auto;
* border: 1px solid #444444;
* */
}
.simpleTree li
{
list-style: none;
margin:0;
padding:0 0 0 34px;
line-height: 14px;
}
.simpleTree li span
{
display:inline;
clear: left;
white-space: nowrap;
}
.simpleTree ul
{
margin:0;
padding:0;
}
.simpleTree .root
{
margin-left:-16px;
background: url($self->{imagePath}root.gif) no-repeat 16px 0 #ffffff;
}
.simpleTree .line
{
margin:0 0 0 -16px;
padding:0;
line-height: 3px;
height:3px;
font-size:3px;
background: url($self->{imagePath}line_bg.gif) 0 0 no-repeat transparent;
}
.simpleTree .line-last
{
margin:0 0 0 -16px;
padding:0;
line-height: 3px;
height:3px;
font-size:3px;
background: url($self->{imagePath}spacer.gif) 0 0 no-repeat transparent;
}
.simpleTree .line-over
{
margin:0 0 0 -16px;
padding:0;
line-height: 3px;
height:3px;
font-size:3px;
background: url($self->{imagePath}line_bg_over.gif) 0 0 no-repeat transparent;
}
.simpleTree .line-over-last
{
margin:0 0 0 -16px;
padding:0;
line-height: 3px;
height:3px;
font-size:3px;
background: url($self->{imagePath}line_bg_over_last.gif) 0 0 no-repeat transparent;
}
.simpleTree .folder-open
{
margin-left:-16px;
background: url($self->{imagePath}collapsable.gif) 0 -2px no-repeat #fff;
}
.simpleTree .folder-open-last
{
margin-left:-16px;
background: url($self->{imagePath}collapsable-last.gif) 0 -2px no-repeat #fff;
}
.simpleTree .folder-close
{
margin-left:-16px;
background: url($self->{imagePath}expandable.gif) 0 -2px no-repeat #fff;
}
.simpleTree .folder-close-last
{
margin-left:-16px;
background: url($self->{imagePath}expandable-last.gif) 0 -2px no-repeat #fff;
}
.simpleTree .doc
{
margin-left:-16px;
background: url($self->{imagePath}leaf.gif) 0 -1px no-repeat #fff;
}
.simpleTree .doc-last
{
margin-left:-16px;
background: url($self->{imagePath}leaf-last.gif) 0 -1px no-repeat #fff;
}
.simpleTree .ajax
{
background: url($self->{imagePath}spinner.gif) no-repeat 0 0 #ffffff;
height: 16px;
display:none;
}
.simpleTree .ajax li
{
display:none;
margin:0;
padding:0;
}
.simpleTree .trigger
{
display:inline;
margin-left:-32px;
width: 28px;
height: 11px;
cursor:pointer;
}
.simpleTree .text
{
cursor: default;
}
.simpleTree .active
{
cursor: default;
background-color:#F7BE77;
padding:0px 2px;
border: 1px dashed #444;
}
#drag_container
{
background:#ffffff;
color:#000;
font: normal 11px arial, tahoma, helvetica, sans-serif;
border: 1px dashed #767676;
}
#drag_container ul
{
list-style: none;
padding:0;
margin:0;
}
#drag_container li
{
list-style: none;
background-color:#ffffff;
line-height:18px;
white-space: nowrap;
padding:1px 1px 0px 16px;
margin:0;
}
#drag_container li span
{
padding:0;
}
#drag_container li.doc, #drag_container li.doc-last
{
background: url($self->{imagePath}leaf.gif) no-repeat -17px 0 #ffffff;
}
#drag_container .folder-close, #drag_container .folder-close-last
{
background: url($self->{imagePath}expandable.gif) no-repeat -17px 0 #ffffff;
}
#drag_container .folder-open, #drag_container .folder-open-last
{
background: url($self->{imagePath}collapsable.gif) no-repeat -17px 0 #ffffff;
}
EOF
}
## @method protected js()
# Display the main javascript file (called by http://manager.example.com/sessions.pl/js)
sub js {
my $self = shift;
print <<"EOF";
/*
* jQuery SimpleTree Drag&Drop plugin
* Update on 22th May 2008
* Version 0.3
*
* Licensed under BSD
* Copyright (c) 2008, Peter Panov , IKEEN Group http://www.ikeen.com
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the Peter Panov, IKEEN Group nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY Peter Panov, IKEEN Group ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL Peter Panov, IKEEN Group BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
\$.fn.simpleTree = function(opt){
return this.each(function(){
var TREE = this;
var ROOT = \$('.root',this);
var mousePressed = false;
var mouseMoved = false;
var dragMoveType = false;
var dragNode_destination = false;
var dragNode_source = false;
var dragDropTimer = false;
var ajaxCache = Array();
TREE.option = {
drag: true,
animate: false,
autoclose: false,
speed: 'fast',
afterAjax: false,
afterMove: false,
afterClick: false,
afterDblClick: false,
// added by Erik Dohmen (2BinBusiness.nl) to make context menu cliks available
afterContextMenu: false,
docToFolderConvert:false
};
TREE.option = \$.extend(TREE.option,opt);
\$.extend(this, {getSelected: function(){
return \$('span.active', this).parent();
}});
TREE.closeNearby = function(obj)
{
\$(obj).siblings().filter('.folder-open, .folder-open-last').each(function(){
var childUl = \$('>ul',this);
var className = this.className;
this.className = className.replace('open','close');
if(TREE.option.animate)
{
childUl.animate({height:"toggle"},TREE.option.speed);
}else{
childUl.hide();
}
});
};
TREE.nodeToggle = function(obj)
{
var childUl = \$('>ul',obj);
if(childUl.is(':visible')){
obj.className = obj.className.replace('open','close');
if(TREE.option.animate)
{
childUl.animate({height:"toggle"},TREE.option.speed);
}else{
childUl.hide();
}
}else{
obj.className = obj.className.replace('close','open');
if(TREE.option.animate)
{
childUl.animate({height:"toggle"},TREE.option.speed, function(){
if(TREE.option.autoclose)TREE.closeNearby(obj);
if(childUl.is('.ajax'))TREE.setAjaxNodes(childUl, obj.id);
});
}else{
childUl.show();
if(TREE.option.autoclose)TREE.closeNearby(obj);
if(childUl.is('.ajax'))TREE.setAjaxNodes(childUl, obj.id);
}
}
};
TREE.setAjaxNodes = function(node, parentId, callback)
{
if(\$.inArray(parentId,ajaxCache) == -1){
ajaxCache[ajaxCache.length]=parentId;
var url = \$.trim(\$('>li', node).text());
if(url && url.indexOf('url:'))
{
url=\$.trim(url.replace(/.*\\{url:(.*)\\}/i ,'\$1'));
\$.ajax({
type: "GET",
url: url,
contentType:'html',
cache:false,
success: function(responce){
node.removeAttr('class');
node.html(responce);
\$.extend(node,{url:url});
TREE.setTreeNodes(node, true);
if(typeof TREE.option.afterAjax == 'function')
{
TREE.option.afterAjax(node);
}
if(typeof callback == 'function')
{
callback(node);
}
}
});
}
}
};
TREE.setTreeNodes = function(obj, useParent){
obj = useParent? obj.parent():obj;
\$('li>span', obj).addClass('text')
.bind('selectstart', function() {
return false;
}).click(function(){
\$('.active',TREE).attr('class','text');
if(this.className=='text')
{
this.className='active';
}
if(typeof TREE.option.afterClick == 'function')
{
TREE.option.afterClick(\$(this).parent());
}
return false;
}).dblclick(function(){
mousePressed = false;
TREE.nodeToggle(\$(this).parent().get(0));
if(typeof TREE.option.afterDblClick == 'function')
{
TREE.option.afterDblClick(\$(this).parent());
}
return false;
// added by Erik Dohmen (2BinBusiness.nl) to make context menu actions
// available
}).bind("contextmenu",function(){
\$('.active',TREE).attr('class','text');
if(this.className=='text')
{
this.className='active';
}
if(typeof TREE.option.afterContextMenu == 'function')
{
TREE.option.afterContextMenu(\$(this).parent());
}
return false;
}).mousedown(function(event){
mousePressed = true;
cloneNode = \$(this).parent().clone();
var LI = \$(this).parent();
if(TREE.option.drag)
{
\$('>ul', cloneNode).hide();
\$('body').append('');
\$('#drag_container').hide().css({opacity:'0.8'});
\$('#drag_container >ul').append(cloneNode);
\$(" ").attr({id : "tree_plus",src : "$self->{imagePath}plus.gif"}).css({width: "7px",display: "block",position: "absolute",left : "5px",top: "5px", display:'none'}).appendTo("body");
\$(document).bind("mousemove", {LI:LI}, TREE.dragStart).bind("mouseup",TREE.dragEnd);
}
return false;
}).mouseup(function(){
if(mousePressed && mouseMoved && dragNode_source)
{
TREE.moveNodeToFolder(\$(this).parent());
}
TREE.eventDestroy();
});
\$('li', obj).each(function(i){
var className = this.className;
var open = false;
var cloneNode=false;
var LI = this;
var childNode = \$('>ul',this);
if(childNode.size()>0){
var setClassName = 'folder-';
if(className && className.indexOf('open')>=0){
setClassName=setClassName+'open';
open=true;
}else{
setClassName=setClassName+'close';
}
this.className = setClassName + (\$(this).is(':last-child')? '-last':'');
if(!open || className.indexOf('ajax')>=0)childNode.hide();
TREE.setTrigger(this);
}else{
var setClassName = 'doc';
this.className = setClassName + (\$(this).is(':last-child')? '-last':'');
}
}).before(' ')
.filter(':last-child').after(' ');
TREE.setEventLine(\$('.line, .line-last', obj));
};
TREE.setTrigger = function(node){
\$('>span',node).before(' ');
var trigger = \$('>.trigger', node);
trigger.click(function(event){
TREE.nodeToggle(node);
});
if(!\$.browser.msie)
{
trigger.css('float','left');
}
};
TREE.dragStart = function(event){
var LI = \$(event.data.LI);
if(mousePressed)
{
mouseMoved = true;
if(dragDropTimer) clearTimeout(dragDropTimer);
if(\$('#drag_container:not(:visible)')){
\$('#drag_container').show();
LI.prev('.line').hide();
dragNode_source = LI;
}
\$('#drag_container').css({position:'absolute', "left" : (event.pageX + 5), "top": (event.pageY + 15) });
if(LI.is(':visible'))LI.hide();
var temp_move = false;
if(event.target.tagName.toLowerCase()=='span' && \$.inArray(event.target.className, Array('text','active','trigger'))!= -1)
{
var parent = event.target.parentNode;
var offs = \$(parent).offset({scroll:false});
var screenScroll = {x : (offs.left - 3),y : event.pageY - offs.top};
var isrc = \$("#tree_plus").attr('src');
var ajaxChildSize = \$('>ul.ajax',parent).size();
var ajaxChild = \$('>ul.ajax',parent);
screenScroll.x += 19;
screenScroll.y = event.pageY - screenScroll.y + 5;
if(parent.className.indexOf('folder-close')>=0 && ajaxChildSize==0)
{
if(isrc.indexOf('minus')!=-1)\$("#tree_plus").attr('src','$self->{imagePath}plus.gif');
\$("#tree_plus").css({"left": screenScroll.x, "top": screenScroll.y}).show();
dragDropTimer = setTimeout(function(){
parent.className = parent.className.replace('close','open');
\$('>ul',parent).show();
}, 700);
}else if(parent.className.indexOf('folder')>=0 && ajaxChildSize==0){
if(isrc.indexOf('minus')!=-1)\$("#tree_plus").attr('src','$self->{imagePath}plus.gif');
\$("#tree_plus").css({"left": screenScroll.x, "top": screenScroll.y}).show();
}else if(parent.className.indexOf('folder-close')>=0 && ajaxChildSize>0)
{
mouseMoved = false;
\$("#tree_plus").attr('src','$self->{imagePath}minus.gif');
\$("#tree_plus").css({"left": screenScroll.x, "top": screenScroll.y}).show();
\$('>ul',parent).show();
/*
Thanks for the idea of Erik Dohmen
*/
TREE.setAjaxNodes(ajaxChild,parent.id, function(){
parent.className = parent.className.replace('close','open');
mouseMoved = true;
\$("#tree_plus").attr('src','$self->{imagePath}plus.gif');
\$("#tree_plus").css({"left": screenScroll.x, "top": screenScroll.y}).show();
});
}else{
if(TREE.option.docToFolderConvert)
{
\$("#tree_plus").css({"left": screenScroll.x, "top": screenScroll.y}).show();
}else{
\$("#tree_plus").hide();
}
}
}else{
\$("#tree_plus").hide();
}
return false;
}
return true;
};
TREE.dragEnd = function(){
if(dragDropTimer) clearTimeout(dragDropTimer);
TREE.eventDestroy();
};
TREE.setEventLine = function(obj){
obj.mouseover(function(){
if(this.className.indexOf('over')<0 && mousePressed && mouseMoved)
{
this.className = this.className.replace('line','line-over');
}
}).mouseout(function(){
if(this.className.indexOf('over')>=0)
{
this.className = this.className.replace('-over','');
}
}).mouseup(function(){
if(mousePressed && dragNode_source && mouseMoved)
{
dragNode_destination = \$(this).parents('li:first');
TREE.moveNodeToLine(this);
TREE.eventDestroy();
}
});
};
TREE.checkNodeIsLast = function(node)
{
if(node.className.indexOf('last')>=0)
{
var prev_source = dragNode_source.prev().prev();
if(prev_source.size()>0)
{
prev_source[0].className+='-last';
}
node.className = node.className.replace('-last','');
}
};
TREE.checkLineIsLast = function(line)
{
if(line.className.indexOf('last')>=0)
{
var prev = \$(line).prev();
if(prev.size()>0)
{
prev[0].className = prev[0].className.replace('-last','');
}
dragNode_source[0].className+='-last';
}
};
TREE.eventDestroy = function()
{
// added by Erik Dohmen (2BinBusiness.nl), the unbind mousemove TREE.dragStart action
// like this other mousemove actions binded through other actions ain't removed (use it myself
// to determine location for context menu)
\$(document).unbind('mousemove',TREE.dragStart).unbind('mouseup').unbind('mousedown');
\$('#drag_container, #tree_plus').remove();
if(dragNode_source)
{
\$(dragNode_source).show().prev('.line').show();
}
dragNode_destination = dragNode_source = mousePressed = mouseMoved = false;
//ajaxCache = Array();
};
TREE.convertToFolder = function(node){
node[0].className = node[0].className.replace('doc','folder-open');
node.append('');
TREE.setTrigger(node[0]);
TREE.setEventLine(\$('.line, .line-last', node));
};
TREE.convertToDoc = function(node){
\$('>ul', node).remove();
\$('img', node).remove();
node[0].className = node[0].className.replace(/folder-(open|close)/gi , 'doc');
};
TREE.moveNodeToFolder = function(node)
{
if(!TREE.option.docToFolderConvert && node[0].className.indexOf('doc')!=-1)
{
return true;
}else if(TREE.option.docToFolderConvert && node[0].className.indexOf('doc')!=-1){
TREE.convertToFolder(node);
}
TREE.checkNodeIsLast(dragNode_source[0]);
var lastLine = \$('>ul >.line-last', node);
if(lastLine.size()>0)
{
TREE.moveNodeToLine(lastLine[0]);
}
};
TREE.moveNodeToLine = function(node){
TREE.checkNodeIsLast(dragNode_source[0]);
TREE.checkLineIsLast(node);
var parent = \$(dragNode_source).parents('li:first');
var line = \$(dragNode_source).prev('.line');
\$(node).before(dragNode_source);
\$(dragNode_source).before(line);
node.className = node.className.replace('-over','');
var nodeSize = \$('>ul >li', parent).not('.line, .line-last').filter(':visible').size();
if(TREE.option.docToFolderConvert && nodeSize==0)
{
TREE.convertToDoc(parent);
}else if(nodeSize==0)
{
parent[0].className=parent[0].className.replace('open','close');
\$('>ul',parent).hide();
}
// added by Erik Dohmen (2BinBusiness.nl) select node
if(\$('span:first',dragNode_source).attr('class')=='text')
{
\$('.active',TREE).attr('class','text');
\$('span:first',dragNode_source).attr('class','active');
}
if(typeof(TREE.option.afterMove) == 'function')
{
var pos = \$(dragNode_source).prevAll(':not(.line)').size();
TREE.option.afterMove(\$(node).parents('li:first'), \$(dragNode_source), pos);
}
};
TREE.addNode = function(id, text, callback)
{
var temp_node = \$(' ');
TREE.setTreeNodes(temp_node);
dragNode_destination = TREE.getSelected();
dragNode_source = \$('.doc-last',temp_node);
TREE.moveNodeToFolder(dragNode_destination);
temp_node.remove();
if(typeof(callback) == 'function')
{
callback(dragNode_destination, dragNode_source);
}
};
TREE.delNode = function(callback)
{
dragNode_source = TREE.getSelected();
TREE.checkNodeIsLast(dragNode_source[0]);
dragNode_source.prev().remove();
dragNode_source.remove();
if(typeof(callback) == 'function')
{
callback(dragNode_destination);
}
};
TREE.init = function(obj)
{
TREE.setTreeNodes(obj, false);
};
TREE.init(ROOT);
});
}
EOF
}
1;
__END__
=head1 NAME
Lemonldap::NG::Manager::Sessions - Perl extension to manage Lemonldap::NG
sessions
=head1 SYNOPSIS
#!/usr/bin/perl
use strict;
use Lemonldap::NG::Manager::Sessions;
our $cgi ||= Lemonldap::NG::Manager::Sessions->new({
localStorage => "Cache::FileCache",
localStorageOptions => {
'namespace' => 'MyNamespace',
'default_expires_in' => 600,
'directory_umask' => '007',
'cache_root' => '/tmp',
'cache_depth' => 5,
},
configStorage => $Lemonldap::NG::Conf::configStorage,
configStorage=>{
type=>'File',
dirName=>"/tmp/",
},
https => 1,
jqueryUri => '/js/jquery/jquery.js',
imagePath => '/js/jquery.simple.tree/',
# Force the use of X-FORWARDED-FOR for IP
useXForwardedForIP => 1,
# Optionnal
protection => 'rule: $uid eq "admin"',
# Or to use rules from manager
protection => 'manager',
# Or just to authenticate without managing authorization
protection => 'authenticate',
});
$cgi->process();
=head1 DESCRIPTION
Lemonldap::NG::Manager::Sessions provides a web interface to manage
Lemonldap::NG sessions.
It inherits from L, so see this manpage to
understand how arguments passed to the constructor.
=head1 SEE ALSO
L, L
=head1 AUTHOR
Xavier Guimard, Ex.guimard@free.frE
=head1 COPYRIGHT AND LICENSE
Copyright (C) 2008 by Xavier Guimard
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.8 or,
at your option, any later version of Perl 5 you may have available.
C embedded javascript library is licensed under BSD
L and copyrighted (c) 2008 by Peter
Panov Epanov@elcat.kgE, IKEEN Group L
=cut