Implement Entry Copy/Move
This commit is contained in:
parent
73f66eb282
commit
86fad448b1
@ -509,4 +509,15 @@ final class Server
|
||||
{
|
||||
return Arr::get($this->rootDSE->subschemasubentry,0);
|
||||
}
|
||||
|
||||
public function subordinates(string $dn,array $attrs=['dn']): ?LDAPCollection
|
||||
{
|
||||
return $this
|
||||
->get(
|
||||
dn: $dn,
|
||||
attrs: array_merge($attrs,[]))
|
||||
->rawFilter('(hassubordinates=TRUE)')
|
||||
->search()
|
||||
->get() ?: NULL;
|
||||
}
|
||||
}
|
@ -113,4 +113,27 @@ class AjaxController extends Controller
|
||||
'may' => $oc->getMayAttrs()->pluck('name'),
|
||||
];
|
||||
}
|
||||
|
||||
public function subordinates(?string $dn=NULL): array
|
||||
{
|
||||
$dn = $dn ? Crypt::decryptString($dn) : '';
|
||||
|
||||
// Sometimes our key has a command, so we'll ignore it
|
||||
if (str_starts_with($dn,'*') && ($x=strpos($dn,'|')))
|
||||
$dn = substr($dn,$x+1);
|
||||
|
||||
$result = collect();
|
||||
// If no DN, we'll find all children
|
||||
if (! $dn)
|
||||
foreach (Server::baseDNs() as $base)
|
||||
$result = $result->merge(config('server')
|
||||
->subordinates($base->getDN()));
|
||||
else
|
||||
$result = config('server')
|
||||
->subordinates(collect(explode(',',$dn))->last());
|
||||
|
||||
return
|
||||
$result->map(fn($item)=>['id'=>$item->getDNSecure(),'value'=>$item->getDN()])
|
||||
->toArray();
|
||||
}
|
||||
}
|
@ -112,6 +112,60 @@ class HomeController extends Controller
|
||||
->with('updated',FALSE);
|
||||
}
|
||||
|
||||
public function entry_copy_move(Request $request): \Illuminate\Http\RedirectResponse|\Illuminate\View\View
|
||||
{
|
||||
$from_dn = Crypt::decryptString($request->post('dn'));
|
||||
Log::info(sprintf('%s:Renaming [%s] to [%s]',self::LOGKEY,$from_dn,$request->post('to_dn')));
|
||||
|
||||
$o = clone config('server')->fetch($from_dn);
|
||||
|
||||
if (! $o)
|
||||
return back()
|
||||
->withInput()
|
||||
->with('note',__('DN doesnt exist'));
|
||||
|
||||
$o->setDN($request->post('to_dn'));
|
||||
$o->exists = FALSE;
|
||||
|
||||
// Add the RDN attribute to match the new RDN
|
||||
$rdn = collect(explode(',',$request->post('to_dn')))->first();
|
||||
|
||||
list($attr,$value) = explode('=',$rdn);
|
||||
$o->{$attr} = [Entry::TAG_NOTAG => $o->getObject($attr)->tagValuesOld(Entry::TAG_NOTAG)->push($value)->unique()];
|
||||
|
||||
Log::info(sprintf('%s:Copying [%s] to [%s]',self::LOGKEY,$from_dn,$o->getDN()));
|
||||
|
||||
try {
|
||||
$o->save();
|
||||
|
||||
} catch (LdapRecordException $e) {
|
||||
return Redirect::to('/')
|
||||
->withInput(['_key'=>Crypt::encryptString('*dn|'.$from_dn)])
|
||||
->with('failed',sprintf('%s: %s - %s: %s',
|
||||
__('LDAP Server Error Code'),
|
||||
$e->getDetailedError()?->getErrorCode() ?: $e->getMessage(),
|
||||
$e->getDetailedError()?->getErrorMessage() ?: $e->getFile(),
|
||||
$e->getDetailedError()?->getDiagnosticMessage() ?: $e->getLine(),
|
||||
));
|
||||
}
|
||||
|
||||
if ($request->post('delete') && $request->post('delete') === '1') {
|
||||
Log::info(sprintf('%s:Deleting [%s] after copy',self::LOGKEY,$from_dn));
|
||||
|
||||
$x = $this->entry_delete($request);
|
||||
|
||||
return ($x->getSession()->has('success'))
|
||||
? Redirect::to('/')
|
||||
->withInput(['_key'=>Crypt::encryptString('*dn|'.$o->getDN())])
|
||||
->with('success',__('Entry copied and deleted'))
|
||||
: $x;
|
||||
}
|
||||
|
||||
return Redirect::to('/')
|
||||
->withInput(['_key'=>Crypt::encryptString('*dn|'.$o->getDN())])
|
||||
->with('success',__('Entry copied'));
|
||||
}
|
||||
|
||||
public function entry_create(EntryAddRequest $request): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$key = $this->request_key($request,collect(old()));
|
||||
@ -154,7 +208,7 @@ class HomeController extends Controller
|
||||
->with('failed',sprintf('%s: %s - %s: %s',
|
||||
__('LDAP Server Error Code'),
|
||||
$e->getDetailedError()->getErrorCode(),
|
||||
__($e->getDetailedError()->getErrorMessage()),
|
||||
$e->getDetailedError()->getErrorMessage(),
|
||||
$e->getDetailedError()->getDiagnosticMessage(),
|
||||
));
|
||||
}
|
||||
@ -377,7 +431,7 @@ class HomeController extends Controller
|
||||
->with('failed',sprintf('%s: %s - %s: %s',
|
||||
__('LDAP Server Error Code'),
|
||||
$e->getDetailedError()->getErrorCode(),
|
||||
__($e->getDetailedError()->getErrorMessage()),
|
||||
$e->getDetailedError()->getErrorMessage(),
|
||||
$e->getDetailedError()->getDiagnosticMessage(),
|
||||
));
|
||||
}
|
||||
@ -435,8 +489,8 @@ class HomeController extends Controller
|
||||
->with('dn',$key['dn'])
|
||||
->with('o',$o)
|
||||
->with('page_actions',collect([
|
||||
'copy'=>FALSE,
|
||||
'create'=>($x=($o->getObjects()->except('entryuuid')->count() > 0)),
|
||||
'copy'=>$x,
|
||||
'delete'=>(! is_null($xx=$o->getObject('hassubordinates')->value)) && ($xx === 'FALSE'),
|
||||
'edit'=>$x,
|
||||
'export'=>$x,
|
||||
|
@ -92,11 +92,12 @@ class Entry extends Model
|
||||
public function getAttributes(): array
|
||||
{
|
||||
return $this->objects
|
||||
->filter(fn($item)=>(! $item->is_internal))
|
||||
->flatMap(fn($item)=>
|
||||
($item->no_attr_tags)
|
||||
$item->no_attr_tags
|
||||
? [strtolower($item->name)=>$item->values]
|
||||
: $item->values
|
||||
->flatMap(fn($v,$k)=>[strtolower($item->name.($k !== self::TAG_NOTAG ? ';'.$k : ''))=>$v]))
|
||||
->flatMap(fn($v,$k)=>[strtolower($item->name.(($k !== self::TAG_NOTAG) ? ';'.$k : ''))=>$v]))
|
||||
->toArray();
|
||||
}
|
||||
|
||||
|
7
public/css/custom.css
vendored
7
public/css/custom.css
vendored
@ -19,7 +19,7 @@ attribute#objectclass .input-group-end:not(input.form-control) {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
.input-group:first-child:not(.select-group) .select2-container--bootstrap-5 .select2-selection {
|
||||
.input-group:first-child:not(.select-group):not(:last-child) .select2-container--bootstrap-5 .select2-selection {
|
||||
border-bottom-right-radius: unset;
|
||||
border-top-right-radius: unset;
|
||||
}
|
||||
@ -97,4 +97,9 @@ input.form-control.input-group-end {
|
||||
/* Square UL items */
|
||||
ul.square {
|
||||
list-style-type: square;
|
||||
}
|
||||
|
||||
/* Fix select2 selections when set up with d-none */
|
||||
select[class*="d-none"] + span.select2 {
|
||||
display: none;
|
||||
}
|
@ -23,7 +23,9 @@
|
||||
@endif
|
||||
@if($page_actions->get('copy'))
|
||||
<li>
|
||||
<button class="btn btn-outline-dark p-1 m-1" id="entry-copy-move" data-bs-toggle="tooltip" data-bs-placement="bottom" title="@lang('Copy/Move')" disabled><i class="fas fa-fw fa-copy fs-5"></i></button>
|
||||
<span id="entry-copy-move" data-bs-toggle="modal" data-bs-target="#page-modal">
|
||||
<button class="btn btn-outline-dark p-1 m-1" data-bs-toggle="tooltip" data-bs-placement="bottom" title="@lang('Copy/Move')"><i class="fas fa-fw fa-copy fs-5"></i></button>
|
||||
</span>
|
||||
</li>
|
||||
@endif
|
||||
@if($page_actions->get('edit'))
|
||||
@ -226,6 +228,25 @@
|
||||
var that = $(this).find('.modal-content');
|
||||
|
||||
switch ($(item.relatedTarget).attr('id')) {
|
||||
case 'entry-copy-move':
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
url: '{{ url('modal/copy-move') }}/'+dn,
|
||||
dataType: 'html',
|
||||
cache: false,
|
||||
beforeSend: function() {
|
||||
that.empty().append('<span class="p-3"><i class="fas fa-3x fa-spinner fa-pulse"></i></span>');
|
||||
},
|
||||
success: function(data) {
|
||||
that.empty().html(data);
|
||||
},
|
||||
error: function(e) {
|
||||
if (e.status !== 412)
|
||||
alert('That didnt work? Please try again....');
|
||||
},
|
||||
});
|
||||
break;
|
||||
|
||||
case 'entry-delete':
|
||||
$.ajax({
|
||||
method: 'GET',
|
||||
|
178
resources/views/modals/entry-copy-move.blade.php
Normal file
178
resources/views/modals/entry-copy-move.blade.php
Normal file
@ -0,0 +1,178 @@
|
||||
<div class="modal-header bg-dark text-white">
|
||||
<h1 class="modal-title fs-5">
|
||||
<i class="fas fa-fw fa-exclamation-triangle"></i> @lang('Rename') <strong>{{ $x=Crypt::decryptString($dn) }}</strong>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<span>
|
||||
@lang('New') DN: <strong><span id="newdn" class="fs-4 opacity-50"><small class="fs-5">[@lang('Select Base')]</small></span></strong>
|
||||
</span>
|
||||
<br>
|
||||
<br>
|
||||
<form id="entry-rename-form" method="POST" action="{{ url('entry/copy-move') }}">
|
||||
@csrf
|
||||
<input type="hidden" name="dn" value="{{ $dn }}">
|
||||
<input type="hidden" name="to_dn" value="">
|
||||
|
||||
<div class="row pb-3">
|
||||
<div class="col-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="delete-checkbox" name="delete" value="1">
|
||||
<label class="form-check-label" for="delete-checkbox">
|
||||
<i class="fas fa-fw fa-trash"></i> @lang('Delete after Copy')
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="input-group mb-3">
|
||||
<span class="input-group-text" id="label">@lang('Select Base of Entry')</span>
|
||||
<input type="text" id="rdn" class="form-control d-none" style="width:20%;" placeholder="{{ $rdn=collect(explode(',',$x))->first() }}" value="{{ $rdn }}">
|
||||
<span class="input-group-text p-1 d-none">,</span>
|
||||
<select class="form-select w-25 d-none" id="rename-subbase" disabled style="width:30%;"></select>
|
||||
<span class="input-group-text p-1 d-none">,</span>
|
||||
<select class="form-select w-25" id="rename-base" style="width:30%;" disabled></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<x-modal.close/>
|
||||
<button id="entry-rename" type="button" class="btn btn-sm btn-primary">@lang('Copy')</button>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
function refreshdn(value) {
|
||||
$('#newdn')
|
||||
.removeClass('opacity-50')
|
||||
.empty()
|
||||
.append(value);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
var rdn = '{{ $rdn }}';
|
||||
var base = '';
|
||||
|
||||
var that=$('#newdn');
|
||||
|
||||
// Get our bases
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: '{{ url('ajax/subordinates') }}',
|
||||
dataType: 'json',
|
||||
cache: false,
|
||||
beforeSend: function() {
|
||||
that.empty().append('<span class="p-3"><i class="fas fa-xs fa-spinner fa-pulse"></i></span>');
|
||||
},
|
||||
success: function(data) {
|
||||
that.empty().html('<small class="fs-5">[@lang('Select Base')]</small>');
|
||||
|
||||
$('#rename-base').children().remove();
|
||||
$('#rename-base').append('<option value=""></option>');
|
||||
data.forEach(function(item) {
|
||||
$('#rename-base').append('<option value="'+item.id+'">'+item.value+'</option>');
|
||||
});
|
||||
|
||||
$('#rename-base').prop('disabled',false);
|
||||
},
|
||||
error: function(e) {
|
||||
if (e.status !== 412)
|
||||
alert('That didnt work? Please try again....');
|
||||
},
|
||||
});
|
||||
|
||||
// The base DN container
|
||||
$('#rename-base').select2({
|
||||
dropdownParent: $('#page-modal'),
|
||||
theme: 'bootstrap-5',
|
||||
dropdownAutoWidth: true,
|
||||
width: 'style',
|
||||
allowClear: false,
|
||||
placeholder: 'Choose Base',
|
||||
})
|
||||
.on('change',function() {
|
||||
$(this).prev().removeClass('d-none');
|
||||
$('#rename-subbase').removeClass('d-none')
|
||||
.prev().removeClass('d-none')
|
||||
.prev().removeClass('d-none');
|
||||
$('#label').empty().append("@lang('Complete Path')");
|
||||
|
||||
base = '';
|
||||
if (x=$('#rename-subbase option:selected').text())
|
||||
base += x+',';
|
||||
base += $('#rename-base option:selected').text();
|
||||
|
||||
refreshdn(rdn+','+base);
|
||||
var newdn = '';
|
||||
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url:'{{ url('ajax/children') }}',
|
||||
data: {_key: $(this).val() },
|
||||
dataType: 'json',
|
||||
cache: false,
|
||||
beforeSend: function() {
|
||||
newdn = that.text();
|
||||
console.log(newdn);
|
||||
that.empty().append('<span class="p-3"><i class="fas fa-xs fa-spinner fa-pulse"></i></span>');
|
||||
},
|
||||
success: function(data) {
|
||||
that.empty().text(newdn);
|
||||
$('#rename-subbase').children().remove();
|
||||
$('#rename-subbase').append('<option value=""></option>');
|
||||
data.forEach(function(item) {
|
||||
$('#rename-subbase').append('<option value="'+item.item+'">'+item.title+'</option>');
|
||||
});
|
||||
|
||||
$('#rename-subbase').prop('disabled',false);
|
||||
},
|
||||
error: function(e) {
|
||||
if (e.status !== 412)
|
||||
alert('That didnt work? Please try again....');
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
// Optional make a child a new branch
|
||||
$('#rename-subbase').select2({
|
||||
dropdownParent: $('#page-modal'),
|
||||
theme: 'bootstrap-5',
|
||||
dropdownAutoWidth: true,
|
||||
width: 'style',
|
||||
allowClear: true,
|
||||
placeholder: 'New Subordinate (optional)',
|
||||
})
|
||||
.on('change',function(item) {
|
||||
base = '';
|
||||
if (x=$('#rename-subbase option:selected').text())
|
||||
base += x+',';
|
||||
base += $('#rename-base option:selected').text();
|
||||
|
||||
refreshdn(rdn+','+base);
|
||||
});
|
||||
|
||||
// Complete the RDN
|
||||
$('#rdn').on('input',function(item) {
|
||||
rdn = $(this).val();
|
||||
refreshdn(rdn+','+base);
|
||||
|
||||
$('button[id=entry-rename]').attr('disabled',! rdn.includes('='));
|
||||
})
|
||||
|
||||
// The submit button text
|
||||
$('input#delete-checkbox').on('change',function() {
|
||||
$('button#entry-rename').html($(this).prop('checked') ? '{{ __('Move') }}' : '{{ __('Copy') }}');
|
||||
});
|
||||
|
||||
// Submit
|
||||
$('button[id=entry-rename]').on('click',function() {
|
||||
$('input[name=to_dn]').val(rdn+','+base);
|
||||
$('form#entry-rename-form').submit();
|
||||
});
|
||||
});
|
||||
</script>
|
@ -43,18 +43,21 @@ Route::controller(HomeController::class)->group(function() {
|
||||
});
|
||||
|
||||
Route::match(['get','post'],'entry/add','entry_add');
|
||||
Route::post('entry/attr/add/{id}','entry_attr_add');
|
||||
Route::post('entry/create','entry_create');
|
||||
Route::post('entry/copy-move','entry_copy_move');
|
||||
Route::post('entry/delete','entry_delete');
|
||||
Route::get('entry/export/{id}','entry_export');
|
||||
Route::post('entry/password/check/','entry_password_check');
|
||||
Route::post('entry/attr/add/{id}','entry_attr_add');
|
||||
Route::post('entry/objectclass/add','entry_objectclass_add');
|
||||
Route::post('entry/rename','entry_rename');
|
||||
|
||||
Route::post('entry/update/commit','entry_update');
|
||||
Route::post('entry/update/pending','entry_pending_update');
|
||||
|
||||
Route::post('import/process/{type}','import');
|
||||
|
||||
Route::view('modal/copy-move/{dn}','modals.entry-copy-move');
|
||||
Route::view('modal/delete/{dn}','modals.entry-delete');
|
||||
Route::view('modal/export/{dn}','modals.entry-export');
|
||||
Route::view('modal/rename/{dn}','modals.entry-rename');
|
||||
@ -69,4 +72,5 @@ Route::controller(AjaxController::class)
|
||||
Route::post('children','children');
|
||||
Route::post('schema/view','schema_view');
|
||||
Route::post('schema/objectclass/attrs/{id}','schema_objectclass_attrs');
|
||||
Route::post('subordinates','subordinates');
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user