Compare commits
4 Commits
29c39e618f
...
543250e1fb
Author | SHA1 | Date | |
---|---|---|---|
543250e1fb | |||
3bf97fc0d1 | |||
3ad4c446ea | |||
ee3cb395c2 |
@ -13,7 +13,7 @@ class Template
|
|||||||
private const LOGKEY = 'T--';
|
private const LOGKEY = 'T--';
|
||||||
|
|
||||||
private(set) string $file;
|
private(set) string $file;
|
||||||
private array $template;
|
private Collection $template;
|
||||||
private(set) bool $invalid = FALSE;
|
private(set) bool $invalid = FALSE;
|
||||||
private(set) string $reason = '';
|
private(set) string $reason = '';
|
||||||
private Collection $on_change_target;
|
private Collection $on_change_target;
|
||||||
@ -31,7 +31,7 @@ class Template
|
|||||||
try {
|
try {
|
||||||
// @todo Load in the proper attribute objects and objectclass objects
|
// @todo Load in the proper attribute objects and objectclass objects
|
||||||
// @todo Make sure we have a structural objectclass, or make the template invalid
|
// @todo Make sure we have a structural objectclass, or make the template invalid
|
||||||
$this->template = json_decode($td->get($file),null,512,JSON_OBJECT_AS_ARRAY|JSON_THROW_ON_ERROR);
|
$this->template = collect(json_decode($td->get($file),null,512,JSON_OBJECT_AS_ARRAY|JSON_THROW_ON_ERROR));
|
||||||
|
|
||||||
} catch (\JsonException $e) {
|
} catch (\JsonException $e) {
|
||||||
$this->invalid = TRUE;
|
$this->invalid = TRUE;
|
||||||
@ -42,12 +42,11 @@ class Template
|
|||||||
public function __get(string $key): mixed
|
public function __get(string $key): mixed
|
||||||
{
|
{
|
||||||
return match ($key) {
|
return match ($key) {
|
||||||
'attributes' => collect(Arr::get($this->template,$key))->keys(),
|
'attributes','objectclasses' => collect($this->template->get($key)),
|
||||||
'enabled' => Arr::get($this->template,$key,FALSE) && (! $this->invalid),
|
'enabled' => $this->template->get($key,FALSE) && (! $this->invalid),
|
||||||
'icon','regexp','title' => Arr::get($this->template,$key),
|
'icon','regexp','title' => $this->template->get($key),
|
||||||
'name' => Str::replaceEnd('.json','',$this->file),
|
'name' => Str::replaceEnd('.json','',$this->file),
|
||||||
'objectclasses' => collect(Arr::get($this->template,$key)),
|
'order' => $this->attributes->map(fn($item)=>Arr::get($item,'order')),
|
||||||
'order' => collect(Arr::get($this->template,'attributes'))->map(fn($item)=>$item['order']),
|
|
||||||
|
|
||||||
default => throw new \Exception('Unknown key: '.$key),
|
default => throw new \Exception('Unknown key: '.$key),
|
||||||
};
|
};
|
||||||
@ -55,7 +54,32 @@ class Template
|
|||||||
|
|
||||||
public function __isset(string $key): bool
|
public function __isset(string $key): bool
|
||||||
{
|
{
|
||||||
return array_key_exists($key,$this->template);
|
return $this->template->has($key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the configuration for an attribute
|
||||||
|
*
|
||||||
|
* @param string $attribute
|
||||||
|
* @return array|NULL
|
||||||
|
*/
|
||||||
|
public function attribute(string $attribute): Collection|NULL
|
||||||
|
{
|
||||||
|
$key = $this->attributes->search(fn($item,$key)=>! strcasecmp($key,$attribute));
|
||||||
|
return collect($this->attributes->get($key));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return an template attributes select options
|
||||||
|
*
|
||||||
|
* @param string $attribute
|
||||||
|
* @return Collection|NULL
|
||||||
|
*/
|
||||||
|
public function attributeOptions(string $attribute): Collection|NULL
|
||||||
|
{
|
||||||
|
return ($x=$this->attribute($attribute)?->get('options'))
|
||||||
|
? collect($x)->map(fn($item,$key)=>['id'=>$key,'value'=>$item])
|
||||||
|
: NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -66,7 +90,7 @@ class Template
|
|||||||
*/
|
*/
|
||||||
public function attributeReadOnly(string $attribute): bool
|
public function attributeReadOnly(string $attribute): bool
|
||||||
{
|
{
|
||||||
return ($x=Arr::get($this->template,'attributes.'.$attribute.'.readonly')) && $x;
|
return ($x=$this->attribute($attribute)?->get('readonly')) && $x;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -77,7 +101,18 @@ class Template
|
|||||||
*/
|
*/
|
||||||
public function attributeTitle(string $attribute): string|NULL
|
public function attributeTitle(string $attribute): string|NULL
|
||||||
{
|
{
|
||||||
return Arr::get($this->template,'attributes.'.$attribute.'.display');
|
return $this->attribute($attribute)?->get('display');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the title we should use for an attribute
|
||||||
|
*
|
||||||
|
* @param string $attribute
|
||||||
|
* @return string|NULL
|
||||||
|
*/
|
||||||
|
public function attributeType(string $attribute): string|NULL
|
||||||
|
{
|
||||||
|
return $this->attribute($attribute)?->get('type');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,8 +7,9 @@ use Illuminate\Http\JsonResponse;
|
|||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\Auth;
|
use Illuminate\Support\Facades\Auth;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use LdapRecord\Auth\BindException;
|
||||||
|
use LdapRecord\Container;
|
||||||
|
|
||||||
use App\Exceptions\InvalidUsage;
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Ldap\Entry;
|
use App\Ldap\Entry;
|
||||||
|
|
||||||
@ -57,8 +58,9 @@ class LoginController extends Controller
|
|||||||
* When attempt to login
|
* When attempt to login
|
||||||
*
|
*
|
||||||
* @param Request $request
|
* @param Request $request
|
||||||
* @return void
|
* @return bool
|
||||||
* @throws InvalidUsage
|
* @throws \LdapRecord\ConnectionException
|
||||||
|
* @throws \LdapRecord\ContainerException
|
||||||
*/
|
*/
|
||||||
public function attemptLogin(Request $request)
|
public function attemptLogin(Request $request)
|
||||||
{
|
{
|
||||||
@ -69,12 +71,26 @@ class LoginController extends Controller
|
|||||||
// If the login failed, and PLA is set to use DN login, check if the entry exists.
|
// If the login failed, and PLA is set to use DN login, check if the entry exists.
|
||||||
// If the entry doesnt exist, it might be the root DN, which cannot be used to login
|
// If the entry doesnt exist, it might be the root DN, which cannot be used to login
|
||||||
if ((! $attempt) && $request->dn && config('pla.login.alert_rootdn',TRUE)) {
|
if ((! $attempt) && $request->dn && config('pla.login.alert_rootdn',TRUE)) {
|
||||||
|
// Double check our credentials, and see if they authenticate
|
||||||
|
try {
|
||||||
|
Container::getInstance()
|
||||||
|
->getConnection()
|
||||||
|
->auth()
|
||||||
|
->bind($request->get(login_attr_name()),$request->get('password'));
|
||||||
|
|
||||||
|
} catch (BindException $e) {
|
||||||
|
// Password incorrect, fail anyway
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
$dn = config('server')->fetch($request->dn);
|
$dn = config('server')->fetch($request->dn);
|
||||||
$o = new Entry;
|
$o = new Entry;
|
||||||
|
|
||||||
if (! $dn && $o->getConnection()->getLdapConnection()->errNo() === 32)
|
if (! $dn && $o->getConnection()->getLdapConnection()->errNo() === 32)
|
||||||
abort(501,'Authentication set to DN, but the DN doesnt exist');
|
abort(501,'Authentication succeeded, but the DN doesnt exist');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $attempt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,7 +57,7 @@ class HomeController extends Controller
|
|||||||
$o->objectclass = [Entry::TAG_NOTAG=>$template->objectclasses->toArray()];
|
$o->objectclass = [Entry::TAG_NOTAG=>$template->objectclasses->toArray()];
|
||||||
|
|
||||||
foreach ($o->getAvailableAttributes()
|
foreach ($o->getAvailableAttributes()
|
||||||
->filter(fn($item)=>$item->names_lc->intersect($template->attributes->map('strtolower'))->count())
|
->filter(fn($item)=>$item->names_lc->intersect($template->attributes->keys()->map('strtolower'))->count())
|
||||||
->sortBy(fn($item)=>Arr::get($template->order,$item->name)) as $ao)
|
->sortBy(fn($item)=>Arr::get($template->order,$item->name)) as $ao)
|
||||||
{
|
{
|
||||||
$o->{$ao->name} = [Entry::TAG_NOTAG=>''];
|
$o->{$ao->name} = [Entry::TAG_NOTAG=>''];
|
||||||
|
@ -49,7 +49,7 @@ class Entry extends Model
|
|||||||
$this->objects = collect();
|
$this->objects = collect();
|
||||||
|
|
||||||
// Load any templates
|
// Load any templates
|
||||||
$this->templates = Cache::remember('templates'.Session::id(),config('ldap.cache.time'),function() {
|
$this->templates = Cache::remember('templates'.Session::id(),config('ldap.cache.time'),function() {
|
||||||
$template_dir = Storage::disk(config('pla.template.dir'));
|
$template_dir = Storage::disk(config('pla.template.dir'));
|
||||||
$templates = collect();
|
$templates = collect();
|
||||||
|
|
||||||
|
@ -14,11 +14,20 @@ attribute#objectclass .input-group-end:not(input.form-control) {
|
|||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group:first-child .select2-container--bootstrap-5 .select2-selection {
|
/* select forms that have nothing next to them */
|
||||||
|
.select-group:first-child .select2-container--bootstrap-5 .select2-selection {
|
||||||
|
border-radius: 4px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group:first-child:not(.select-group) .select2-container--bootstrap-5 .select2-selection {
|
||||||
border-bottom-right-radius: unset;
|
border-bottom-right-radius: unset;
|
||||||
border-top-right-radius: unset;
|
border-top-right-radius: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select2-container .select2-selection--single .select2-selection__rendered {
|
||||||
|
font-size: 0.88em;
|
||||||
|
}
|
||||||
|
|
||||||
input.form-control.input-group-end {
|
input.form-control.input-group-end {
|
||||||
border-bottom-right-radius: 4px !important;
|
border-bottom-right-radius: 4px !important;
|
||||||
border-top-right-radius: 4px !important;
|
border-top-right-radius: 4px !important;
|
||||||
|
@ -9,13 +9,16 @@
|
|||||||
<strong class="align-middle"><abbr title="{{ (($x=$template?->attributeTitle($o->name)) ? $o->name.': ' : '').$o->description }}">{{ $x ?: $o->name }}</abbr></strong>
|
<strong class="align-middle"><abbr title="{{ (($x=$template?->attributeTitle($o->name)) ? $o->name.': ' : '').$o->description }}">{{ $x ?: $o->name }}</abbr></strong>
|
||||||
@if($new)
|
@if($new)
|
||||||
@if($template?->attributeReadOnly($o->name_lc))
|
@if($template?->attributeReadOnly($o->name_lc))
|
||||||
<sup data-bs-toggle="tooltip" title="@lang('Input disabled by template')"><i class="fas fa-ban"></i></sup>
|
<sup data-bs-toggle="tooltip" title="@lang('Input disabled')"><i class="fas fa-ban"></i></sup>
|
||||||
@endif
|
@endif
|
||||||
@if($template?->onChangeAttribute($o->name_lc))
|
@if($ca=$template?->onChangeAttribute($o->name_lc))
|
||||||
<sup data-bs-toggle="tooltip" title="@lang('Value triggers an update to another attribute by template')"><i class="fas fa-keyboard"></i></sup>
|
<sup data-bs-toggle="tooltip" title="@lang('Value triggers an update to another attribute')"><i class="fas fa-keyboard"></i></sup>
|
||||||
@endif
|
@endif
|
||||||
@if ($template?->onChangeTarget($o->name_lc))
|
@if ($ct=$template?->onChangeTarget($o->name_lc))
|
||||||
<sup data-bs-toggle="tooltip" title="@lang('Value calculated by template')"><i class="fas fa-wand-magic-sparkles"></i></sup>
|
<sup data-bs-toggle="tooltip" title="@lang('Value calculated from another attribute')"><i class="fas fa-wand-magic-sparkles"></i></sup>
|
||||||
|
@endif
|
||||||
|
@if((! $ca) && (! $ct) && $template?->attribute($o->name_lc))
|
||||||
|
<sup data-bs-toggle="tooltip" title="@lang('Attribute controlled by template')"><i class="fas fa-wand-magic"></i></sup>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@ -59,7 +62,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<x-attribute :o="$o" :edit="(! $template?->attributeReadOnly($o->name)) && $edit" :new="$new" :updated="$updated"/>
|
@switch($template?->attributeType($o->name))
|
||||||
|
@case('select')
|
||||||
|
<x-attribute.template.select :o="$o" :template="$template" :edit="(! $template?->attributeReadOnly($o->name)) && $edit" :new="$new"/>
|
||||||
|
@break;
|
||||||
|
|
||||||
|
@default
|
||||||
|
<x-attribute :o="$o" :edit="(! $template?->attributeReadOnly($o->name)) && $edit" :new="$new" :updated="$updated"/>
|
||||||
|
@endswitch
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
@foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) as $key => $value)
|
@foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) as $key => $value)
|
||||||
@if($edit)
|
@if($edit)
|
||||||
<div class="input-group has-validation">
|
<div class="input-group has-validation">
|
||||||
<x-form.select id="userpassword_hash_{{$loop->index}}{{$template?->name ?? ''}}" name="_userpassword_hash[{{ $langtag }}][]" :value="$o->hash($new ? '' : ($value ?? ''))->id()" :options="$helpers" allowclear="false" :disabled="! $new"/>
|
<x-form.select id="userpassword_hash_{{$loop->index}}{{$template?->name ?: ''}}" name="_userpassword_hash[{{ $langtag }}][]" :value="$o->hash($new ? '' : ($value ?? ''))->id()" :options="$helpers" allowclear="false" :disabled="! $new"/>
|
||||||
<input type="password" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value),'bg-success-subtle'=>$updated]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ Arr::get(old($o->name_lc),$langtag.'.'.$loop->index,$value ? md5($value) : '') }}" @readonly(! $new)>
|
<input type="password" @class(['form-control','is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value),'bg-success-subtle'=>$updated]) name="{{ $o->name_lc }}[{{ $langtag }}][]" value="{{ Arr::get(old($o->name_lc),$langtag.'.'.$loop->index,$value ? md5($value) : '') }}" @readonly(! $new)>
|
||||||
|
|
||||||
<div class="invalid-feedback pb-2">
|
<div class="invalid-feedback pb-2">
|
||||||
@ -24,7 +24,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="offset-1 col-4">
|
<div class="offset-1 col-4">
|
||||||
<span class="p-0 m-0">
|
<span class="p-0 m-0">
|
||||||
<button id="entry-userpassword-check" type="button" class="btn btn-sm btn-outline-dark mt-3" data-bs-toggle="modal" data-bs-target="#page-modal"><i class="fas fa-user-check"></i> @lang('Check Password')</button>
|
<button name="entry-userpassword-check" type="button" class="btn btn-sm btn-outline-dark mt-3" data-bs-toggle="modal" data-bs-target="#page-modal"><i class="fas fa-user-check"></i> @lang('Check Password')</button>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
<!-- $o=Attribute::class -->
|
||||||
|
<x-attribute.layout :edit="$edit" :new="$new" :o="$o">
|
||||||
|
@foreach($o->langtags as $langtag)
|
||||||
|
@foreach(($o->tagValues($langtag)->count() ? $o->tagValues($langtag) : [$langtag => NULL]) as $key => $value)
|
||||||
|
@if($edit)
|
||||||
|
<div class="select-group">
|
||||||
|
<x-form.select
|
||||||
|
@class(['is-invalid'=>($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index)),'mb-1','border-focus'=>! $o->tagValuesOld($langtag)->contains($value)])
|
||||||
|
id="{{ $o->name_lc }}_{{$loop->index}}{{$template?->name ?: ''}}"
|
||||||
|
name="{{ $o->name_lc }}[{{ $langtag }}][]"
|
||||||
|
:value="$value"
|
||||||
|
:options="$template->attributeOptions($o->name_lc)"
|
||||||
|
allowclear="true"
|
||||||
|
:disabled="! $new"
|
||||||
|
:readonly="FALSE"/>
|
||||||
|
|
||||||
|
<div class="invalid-feedback pb-2">
|
||||||
|
@if($e=$errors->get($o->name_lc.'.'.$langtag.'.'.$loop->index))
|
||||||
|
{{ join('|',$e) }}
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
{{ $o->render_item_old($langtag.'.'.$key) }}
|
||||||
|
@endif
|
||||||
|
@endforeach
|
||||||
|
@endforeach
|
||||||
|
</x-attribute.layout>
|
@ -1,7 +1,7 @@
|
|||||||
@extends('architect::layouts.error')
|
@extends('architect::layouts.error')
|
||||||
|
|
||||||
@section('error')
|
@section('error')
|
||||||
501: @lang('LDAP Authentication Error')
|
501: @lang('LDAP User Error')
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
@php($up=(session()->get('updated') ?: collect()))
|
@php($up=(session()->get('updated') ?: collect()))
|
||||||
|
|
||||||
@foreach($o->getVisibleAttributes()->filter(fn($item)=>$template->attributes->map('strtolower')->contains($item->name_lc)) as $ao)
|
@foreach($o->getVisibleAttributes()->filter(fn($item)=>$template->attributes->keys()->map('strtolower')->contains($item->name_lc)) as $ao)
|
||||||
<x-attribute-type :o="$ao" :edit="TRUE" :new="FALSE" :template="$template" :updated="$up->contains($ao->name)"/>
|
<x-attribute-type :o="$ao" :edit="TRUE" :new="FALSE" :template="$template" :updated="$up->contains($ao->name)"/>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
@ -282,27 +282,30 @@
|
|||||||
})
|
})
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'entry-userpassword-check':
|
|
||||||
$.ajax({
|
|
||||||
method: 'GET',
|
|
||||||
url: '{{ url('modal/userpassword-check') }}/'+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;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
console.log('No action for button:'+$(item.relatedTarget).attr('id'));
|
switch ($(item.relatedTarget).attr('name')) {
|
||||||
|
case 'entry-userpassword-check':
|
||||||
|
$.ajax({
|
||||||
|
method: 'GET',
|
||||||
|
url: '{{ url('modal/userpassword-check') }}/'+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;
|
||||||
|
|
||||||
|
default:
|
||||||
|
console.log('No action for button:'+$(item.relatedTarget).attr('id'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,31 +1,32 @@
|
|||||||
{
|
{
|
||||||
"title": "Example entry",
|
"title": "Example entry", // Title shown when selecting tempaltes
|
||||||
"description": "This is the description",
|
"description": "This is the description", // Unused, only for documenting
|
||||||
"enabled": false,
|
"enabled": false, // Whether template is enabled or not
|
||||||
"icon": "fa-star-of-life",
|
"icon": "fa-star-of-life", // Icon shown when rendering an existing entry that identifies as this template
|
||||||
"rdn": "o",
|
"rdn": "o", // @todo not implemented
|
||||||
"regexp": "/^$/",
|
"regexp": "/^$/", // Regular expression that restricts where this template cna be used
|
||||||
|
|
||||||
"objectclasses": [
|
"objectclasses": [ // Objectclasses that entries will have if they use this template
|
||||||
"organization"
|
"organization"
|
||||||
],
|
],
|
||||||
|
|
||||||
"attributes": {
|
"attributes": { // Attribute configuration
|
||||||
"attribute1": {
|
"attribute1": { // LDAP attribute name
|
||||||
"display": "Attribute 1",
|
"display": "Attribute 1", // Displayed when accepting input for this value
|
||||||
"hint": "This is an example",
|
"hint": "This is an example", // @todo not implemented
|
||||||
"order": 1
|
"type": null, // Default is NULL, so use normal Attribute rendering type
|
||||||
|
"order": 1 // Order to show attributes
|
||||||
},
|
},
|
||||||
"attribute2": {
|
"attribute2": {
|
||||||
"display": "Attribute 2",
|
"display": "Attribute 2",
|
||||||
"hint": "This is an example",
|
"hint": "This is an example",
|
||||||
"type": "input", // Default is input
|
"type": "input", // Force attribute to use template input
|
||||||
"order": 2
|
"order": 2
|
||||||
},
|
},
|
||||||
"attribute3": {
|
"attribute3": {
|
||||||
"display": "Attribute 3",
|
"display": "Attribute 3",
|
||||||
"type": "select",
|
"type": "select", // Force attribute to use template select
|
||||||
"options": {
|
"options": { // Select options
|
||||||
"/bin/bash": "Bash",
|
"/bin/bash": "Bash",
|
||||||
"/bin/csh": "C Shell",
|
"/bin/csh": "C Shell",
|
||||||
"/bin/dash": "Dash",
|
"/bin/dash": "Dash",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user