mirror of https://github.com/MISP/MISP
chg: [decaying] Added list of available formulas and model settings -
WiPpull/5032/head
parent
878b46811c
commit
b415f8ffc7
|
@ -144,10 +144,33 @@ class DecayingModelController extends AppController
|
|||
|
||||
if ($this->request->is('post') || $this->request->is('put')) {
|
||||
$this->request->data['DecayingModel']['id'] = $id;
|
||||
|
||||
if (!isset($this->request->data['DecayingModel']['formula'])) {
|
||||
$this->request->data['DecayingModel']['formula'] = 'polynomial';
|
||||
}
|
||||
|
||||
if ($this->request->data['DecayingModel']['formula'] == 'polynomial') {
|
||||
if (isset($this->request->data['DecayingModel']['parameters']['settings'])) {
|
||||
$this->request->data['DecayingModel']['parameters']['settings'] = array();
|
||||
}
|
||||
} else if (
|
||||
isset($this->request->data['DecayingModel']['parameters']['settings']) &&
|
||||
$this->request->data['DecayingModel']['parameters']['settings'] == ''
|
||||
) {
|
||||
$this->request->data['DecayingModel']['parameters']['settings'] = '{}';
|
||||
}
|
||||
|
||||
if (!isset($this->request->data['DecayingModel']['parameters'])) {
|
||||
$this->request->data['DecayingModel']['parameters'] = array();
|
||||
} else {
|
||||
if (isset($this->request->data['DecayingModel']['parameters']['settings'])) {
|
||||
$settings = json_decode($this->request->data['DecayingModel']['parameters']['settings'], true);
|
||||
if ($settings === null) {
|
||||
$this->Flash->error(__('Invalid JSON `Settings`.'));
|
||||
return false;
|
||||
}
|
||||
$this->request->data['DecayingModel']['parameters']['settings'] = $settings;
|
||||
}
|
||||
if (!isset($this->request->data['DecayingModel']['parameters']['tau'])) {
|
||||
$this->Flash->error(__('Invalid parameter `tau`.'));
|
||||
return false;
|
||||
|
@ -175,9 +198,9 @@ class DecayingModelController extends AppController
|
|||
$this->request->data['DecayingModel']['parameters']['base_score_config'] = new stdClass();
|
||||
}
|
||||
}
|
||||
$this->request->data['DecayingModel']['parameters'] = json_encode($this->request->data['DecayingModel']['parameters']);
|
||||
|
||||
$fieldList = array('name', 'description', 'parameters');
|
||||
$this->request->data['DecayingModel']['parameters'] = json_encode($this->request->data['DecayingModel']['parameters']);
|
||||
$fieldList = array('name', 'description', 'parameters', 'formula');
|
||||
if ($this->DecayingModel->save($this->request->data, true, $fieldList)) {
|
||||
if ($this->request->is('ajax')) {
|
||||
$saved = $this->DecayingModel->checkAuthorisation($this->Auth->user(), $this->DecayingModel->id);
|
||||
|
@ -292,7 +315,9 @@ class DecayingModelController extends AppController
|
|||
$types = array_merge($types, $objectTypes);
|
||||
ksort($types);
|
||||
$savedDecayingModels = $this->DecayingModel->fetchAllowedModels($this->Auth->user());
|
||||
$available_formulas = $this->DecayingModel->listAvailableFormulas();
|
||||
|
||||
$this->set('available_formulas', $available_formulas);
|
||||
$this->set('parameters', $parameters);
|
||||
$this->set('types', $types);
|
||||
$this->set('savedModels', $savedDecayingModels);
|
||||
|
|
|
@ -99,6 +99,8 @@ class DecayingModel extends AppModel
|
|||
$this->__validateParameters($parameters[$name]);
|
||||
} else if (is_numeric($value)) {
|
||||
$parameters[$name] = round($value, 4);
|
||||
} else if (!empty($value)) {
|
||||
$parameters[$name] = $value;
|
||||
} else {
|
||||
$parameters[$name] = 0;
|
||||
}
|
||||
|
@ -249,20 +251,37 @@ class DecayingModel extends AppModel
|
|||
);
|
||||
}
|
||||
|
||||
private function __include_formula_file_and_return_instance($filename='polynomial.php')
|
||||
private function __include_formula_file_and_return_instance($filename='Polynomial.php')
|
||||
{
|
||||
$filename_no_extension = str_replace('.php', '', $filename);
|
||||
$filename = preg_replace('/[^a-zA-Z0-9_]+/', '-', $filename_no_extension) . '.php'; // sanitization
|
||||
$full_path = APP . 'Model/DecayingModelsFormulas/' . $filename;
|
||||
$expected_classname = 'DecayingModel' . str_replace('.php', '', $filename);
|
||||
$expected_classname = $filename_no_extension;
|
||||
if (is_file($full_path)) {
|
||||
include $full_path;
|
||||
$model_class = ClassRegistry::init($expected_classname);
|
||||
if ($model_class->checkLoading() == 'BONFIRE LIT') {
|
||||
if ($model_class->checkLoading() === 'BONFIRE LIT') {
|
||||
return $model_class;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function listAvailableFormulas()
|
||||
{
|
||||
$path = APP . 'Model/DecayingModelsFormulas/';
|
||||
$formula_files = array_diff(scandir($path), array('..', '.', 'Base.php'));
|
||||
$available_formulas = array();
|
||||
foreach ($formula_files as $formula_file) {
|
||||
$model_class = $this->__include_formula_file_and_return_instance($formula_file);
|
||||
if ($model_class === false) {
|
||||
continue;
|
||||
}
|
||||
$available_formulas[get_class($model_class)] = $model_class::EXTENDS_FORMULA;
|
||||
}
|
||||
return $available_formulas;
|
||||
}
|
||||
|
||||
public function getModelClass($model)
|
||||
{
|
||||
$formula_name = $model['DecayingModel']['formula'] === '' ? 'polynomial' : $model['DecayingModel']['formula'];
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
abstract class DecayingModelBase
|
||||
{
|
||||
public const EXTENDS_FORMULA = '';
|
||||
|
||||
public function checkLoading()
|
||||
{
|
||||
return 'BONFIRE LIT';
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
<?php
|
||||
include_once 'Base.php';
|
||||
|
||||
class DecayingModelPolynomial extends DecayingModelBase
|
||||
class Polynomial extends DecayingModelBase
|
||||
{
|
||||
|
||||
public const EXTENDS_FORMULA = 'Polynomial';
|
||||
|
||||
public function computeScore($model, $attribute, $base_score, $elapsed_time)
|
||||
{
|
||||
if ($elapsed_time < 0) {
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
include_once 'Base.php';
|
||||
|
||||
class PolynomialExtended extends DecayingModelBase
|
||||
{
|
||||
|
||||
public const EXTENDS_FORMULA = 'Polynomial';
|
||||
|
||||
public function computeScore($model, $attribute, $base_score, $elapsed_time)
|
||||
{
|
||||
if ($elapsed_time < 0) {
|
||||
return 0;
|
||||
}
|
||||
$delta = $model['DecayingModel']['parameters']['delta'];
|
||||
$tau = $model['DecayingModel']['parameters']['tau']*24*60*60;
|
||||
$score = $base_score * (1 - pow($elapsed_time / $tau, 1 / $delta));
|
||||
return $score < 0 ? 0 : $score;
|
||||
}
|
||||
|
||||
public function isDecayed($model, $attribute, $score)
|
||||
{
|
||||
$threshold = $model['DecayingModel']['parameters']['threshold'];
|
||||
return $threshold > $score;
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -8,8 +8,9 @@
|
|||
echo $this->Form->input('description', array(
|
||||
));
|
||||
echo $this->Form->input('formula', array(
|
||||
'value' => isset($this->request->data['DecayingModel']['formula']) ? $this->request->data['DecayingModel']['formula'] : 'default'
|
||||
'value' => isset($this->request->data['DecayingModel']['formula']) ? $this->request->data['DecayingModel']['formula'] : 'polynomial'
|
||||
));
|
||||
echo '<div id="ContainerPolynomialSetting">';
|
||||
echo $this->Form->input('DecayingModel.parameters.tau', array(
|
||||
'label' => __('Tau parameter'),
|
||||
'type' => 'number',
|
||||
|
@ -54,6 +55,16 @@
|
|||
'cols' => '10',
|
||||
'value' => isset($this->request->data['DecayingModel']['parameters']['base_score_config']) ? json_encode($this->request->data['DecayingModel']['parameters']['base_score_config']) : ''
|
||||
));
|
||||
echo '</div>';
|
||||
echo '<div id="ContainerOtherSetting">';
|
||||
echo '<div class="clear"></div>';
|
||||
echo '<label for="DecayingModelOtherSettings">' . __('Model Settings') . '</label>';
|
||||
echo $this->Form->textarea('DecayingModel.parameters.settings', array(
|
||||
'class' => 'form-control span6',
|
||||
'cols' => '10',
|
||||
'value' => isset($this->request->data['DecayingModel']['parameters']['settings']) ? json_encode($this->request->data['DecayingModel']['parameters']['settings']) : ''
|
||||
));
|
||||
echo '</div>';
|
||||
?>
|
||||
<div class="clear"></div>
|
||||
</fieldset>
|
||||
|
@ -65,3 +76,18 @@
|
|||
<?php
|
||||
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'decayingModel', 'menuItem' => 'add'));
|
||||
?>
|
||||
<script>
|
||||
toggleOtherSetting();
|
||||
$(document).ready(function() {
|
||||
$('#DecayingModelFormula').on('input', function() {
|
||||
toggleOtherSetting();
|
||||
})
|
||||
});
|
||||
function toggleOtherSetting() {
|
||||
if ($('#DecayingModelFormula').val() === 'Polynomial') {
|
||||
$('#ContainerOtherSetting').hide();
|
||||
} else {
|
||||
$('#ContainerOtherSetting').show();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -64,54 +64,66 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="span12">
|
||||
<div class="span10" style="border: 1px solid #ddd; border-radius: 4px; margin-bottom: 20px;">
|
||||
<div id="decayGraph" style="width: 100%;"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="span6" style="margin-bottom: 20px;">
|
||||
<?php foreach ($parameters as $param => $config): ?>
|
||||
<div class="input-prepend input-append">
|
||||
<span class="add-on param-name" data-toggle="tooltip" data-placement="left" style="min-width: 100px;" title="<?php echo isset($config['info']) ? h($config['info']) : ''?>">
|
||||
<?php echo h($config['name']) . (isset($config['greek']) ? ' <strong>'.h($config['greek']).'</strong>' : ''); ?>
|
||||
</span>
|
||||
<input id="input_<?php echo h($param); ?>" class="input-mini" type="number" min=0 step=<?php echo h($config['step']); ?> value=<?php echo h($config['value']); ?> oninput="refreshGraph(this);" ></input>
|
||||
<span class="add-on"><input id="input_<?php echo h($param); ?>_range" type="range" min=0 <?php echo isset($config['max']) ? 'max=' . $config['max'] : '' ?> step=<?php echo h($config['step']); ?> value=<?php echo h($config['value']); ?> oninput="$('#input_<?php echo h($param); ?>').val(this.value).trigger('input');"></input></span>
|
||||
<?php if (isset($config['unit'])): ?>
|
||||
<span class="add-on"><?php echo h($config['unit']); ?></span>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<select id="formulaSelectPicker">
|
||||
<?php foreach ($available_formulas as $formula_name => $extends_smthg): ?>
|
||||
<option value="<?php echo h($formula_name); ?>" data-extends="<?php echo h($extends_smthg); ?>"><?php echo h($formula_name); ?></option>
|
||||
<?php endforeach; ?>
|
||||
<input id="input_default_base_score" value=0 class="hidden"></input>
|
||||
<div class="input-append" style="margin-bottom: 0px;">
|
||||
<input id="input_base_score_config" class="hidden" value="[]"></input>
|
||||
<button class="btn btn-primary" style="border-radius: 4px 0px 0px 4px;" onclick="decayingTool.toggleBasescoreForm()">
|
||||
<span class="fa fa-tags"> <?php echo __('Adjust base score'); ?></span>
|
||||
</button>
|
||||
<span id="summary_base_score_config" class="add-on param-name">
|
||||
<span class="far fa-square"></span>
|
||||
</span>
|
||||
</select>
|
||||
</div>
|
||||
<div id="containerFormulaPolynomialSetting">
|
||||
<div class="span10" style="border: 1px solid #ddd; border-radius: 4px; margin-bottom: 20px;">
|
||||
<div id="decayGraph" style="width: 100%;"></div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="span6" style="margin-bottom: 20px;">
|
||||
<?php foreach ($parameters as $param => $config): ?>
|
||||
<div class="input-prepend input-append">
|
||||
<span class="add-on param-name" data-toggle="tooltip" data-placement="left" style="min-width: 100px;" title="<?php echo isset($config['info']) ? h($config['info']) : ''?>">
|
||||
<?php echo h($config['name']) . (isset($config['greek']) ? ' <strong>'.h($config['greek']).'</strong>' : ''); ?>
|
||||
</span>
|
||||
<input id="input_<?php echo h($param); ?>" class="input-mini" type="number" min=0 step=<?php echo h($config['step']); ?> value=<?php echo h($config['value']); ?> oninput="refreshGraph(this);" ></input>
|
||||
<span class="add-on"><input id="input_<?php echo h($param); ?>_range" type="range" min=0 <?php echo isset($config['max']) ? 'max=' . $config['max'] : '' ?> step=<?php echo h($config['step']); ?> value=<?php echo h($config['value']); ?> oninput="$('#input_<?php echo h($param); ?>').val(this.value).trigger('input');"></input></span>
|
||||
<?php if (isset($config['unit'])): ?>
|
||||
<span class="add-on"><?php echo h($config['unit']); ?></span>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<input id="input_default_base_score" value=0 class="hidden"></input>
|
||||
<div class="input-append" style="margin-bottom: 0px;">
|
||||
<input id="input_base_score_config" class="hidden" value="[]"></input>
|
||||
<button class="btn btn-primary" style="border-radius: 4px 0px 0px 4px;" onclick="decayingTool.toggleBasescoreForm()">
|
||||
<span class="fa fa-tags"> <?php echo __('Adjust base score'); ?></span>
|
||||
</button>
|
||||
<span id="summary_base_score_config" class="add-on param-name">
|
||||
<span class="far fa-square"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div style="display: inline-block; margin-left: 10px;">
|
||||
<a id="button-toggle-simulation" target="_blank" class="btn btn-primary" href="" onclick="return !$(this).hasClass('disabled');">
|
||||
<span class="fa fa-chart-line"> <?php echo __('Simulate this model'); ?></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: inline-block; margin-left: 10px;">
|
||||
<a id="button-toggle-simulation" target="_blank" class="btn btn-primary" href="" onclick="return !$(this).hasClass('disabled');">
|
||||
<span class="fa fa-chart-line"> <?php echo __('Simulate this model'); ?></span>
|
||||
</a>
|
||||
<div class="span6">
|
||||
<table class="table table-striped table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Expire after (lifetime)</td>
|
||||
<td id="infoCellExpired"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Score halved after (Half-life)</td>
|
||||
<td id="infoCellHalved"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="span6">
|
||||
<table class="table table-striped table-bordered">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Expire after (lifetime)</td>
|
||||
<td id="infoCellExpired"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Score halved after (Half-life)</td>
|
||||
<td id="infoCellHalved"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div id="containerFormulaOtherSetting" class="hidden">
|
||||
<textarea id="textarea_other_settings_formulas" style="width: 430px;" rows="5" placeholder="<?php echo(__('Model\'s Settings')); ?>"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
@ -156,5 +168,23 @@ $(document).ready(function() {
|
|||
}
|
||||
$(this).html(parsedJson);
|
||||
});
|
||||
|
||||
$('#formulaSelectPicker').change(function() {
|
||||
toggleContainer();
|
||||
})
|
||||
|
||||
});
|
||||
function toggleContainer() {
|
||||
var $option = $('#formulaSelectPicker').find('option:selected');
|
||||
if ($option.data('extends') == 'Polynomial') {
|
||||
$('#containerFormulaPolynomialSetting').show();
|
||||
} else {
|
||||
$('#containerFormulaPolynomialSetting').hide();
|
||||
}
|
||||
if ($('#formulaSelectPicker').val() == 'Polynomial') {
|
||||
$('#containerFormulaOtherSetting').hide();
|
||||
} else {
|
||||
$('#containerFormulaOtherSetting').show();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -345,11 +345,16 @@
|
|||
$('#input_Tau').data('multiplier', $('#input_Tau').val()/this.options.TICK_NUM);
|
||||
$('#input_Delta, #input_Delta_range').val(model.parameters.delta);
|
||||
$('#input_Threshold, #input_Threshold_range').val(model.parameters.threshold);
|
||||
$('#input_base_score_config').val(JSON.stringify(model.parameters.base_score_config));
|
||||
var base_score_config = model.parameters.base_score_config === undefined ? {} : model.parameters.base_score_config;
|
||||
$('#input_base_score_config').val(JSON.stringify(base_score_config));
|
||||
var model_settings = model.parameters.settings === undefined ? {} : model.parameters.settings;
|
||||
$('#textarea_other_settings_formulas').val(JSON.stringify(model_settings));
|
||||
$('#input_default_base_score').val(model.parameters.default_base_score);
|
||||
$('#formulaSelectPicker').val(model.formula);
|
||||
var $form = $('#saveForm');
|
||||
$form.find('[name="name"]').val(model.name);
|
||||
$form.find('[name="description"]').val(model.description);
|
||||
toggleContainer();
|
||||
this.refreshInfoCells();
|
||||
this.redrawGraph();
|
||||
// highlight attribute types
|
||||
|
@ -362,6 +367,7 @@
|
|||
var data = {};
|
||||
data.name = $form.find('[name="name"]').val();
|
||||
data.description = $form.find('[name="description"]').val();
|
||||
data.formula = $('#formulaSelectPicker').val();
|
||||
var params = {};
|
||||
params.tau = parseInt($('#input_Tau').val());
|
||||
params.delta = parseFloat($('#input_Delta').val());
|
||||
|
@ -370,6 +376,9 @@
|
|||
var base_score_config = $('#input_base_score_config').val();
|
||||
base_score_config = base_score_config === '' ? '{}' : base_score_config;
|
||||
params.base_score_config = JSON.parse(base_score_config)
|
||||
var model_settings = $('#textarea_other_settings_formulas').val();
|
||||
model_settings = model_settings === '' ? '{}' : model_settings;
|
||||
params.settings = JSON.parse(model_settings)
|
||||
data.parameters = params;
|
||||
return data;
|
||||
},
|
||||
|
@ -409,7 +418,7 @@
|
|||
var post_url = $form.attr('action');
|
||||
if (baseurl.includes('decayingModelMapping')) {
|
||||
that.injectDataAttributeTypes($form, formData);
|
||||
} else if (action.includes('able')) {
|
||||
} else if (action.includes('able')) { // if enable/disable model
|
||||
// do nothing, form filled already
|
||||
} else {
|
||||
that.injectDataModel($form, formData);
|
||||
|
@ -840,13 +849,15 @@ ModelTable.prototype = {
|
|||
{name: 'Model Name'},
|
||||
{name: 'Org ID'},
|
||||
{name: 'Description'},
|
||||
{name: 'Formula'},
|
||||
{name: 'Parameters',
|
||||
children: [
|
||||
{name: 'Tau'},
|
||||
{name: 'Delta'},
|
||||
{name: 'Threshold'},
|
||||
{name: 'Default basescore'},
|
||||
{name: 'Basescore config'}
|
||||
{name: 'Basescore config'},
|
||||
{name: 'Settings'}
|
||||
]
|
||||
},
|
||||
{name: '# Types'},
|
||||
|
@ -909,11 +920,11 @@ ModelTable.prototype = {
|
|||
attr += 'data-'+ k + '=\"' + v + '\" ';
|
||||
});
|
||||
}
|
||||
return '<span class="' + td_class + '" ' + attr + '>' + text + '</span>';
|
||||
return '<span class="' + td_class + '" ' + attr + '>' + (text !== undefined ? text : '') + '</span>';
|
||||
},
|
||||
_gen_td_link: function(url, text, td_class) {
|
||||
td_class = td_class !== undefined ? td_class : '';
|
||||
return '<span class="' + td_class + '"><a href="' + url + '">' + text + '</a></span>';
|
||||
return '<span class="' + td_class + '"><a href="' + url + '">' + (text !== undefined ? text : '') + '</a></span>';
|
||||
},
|
||||
_gen_td_buttons: function(model) {
|
||||
var html_button = '<div style="width: max-content">';
|
||||
|
@ -932,6 +943,10 @@ ModelTable.prototype = {
|
|||
if (!Array.isArray(model.DecayingModel.parameters.base_score_config) && typeof model.DecayingModel.parameters.base_score_config === 'object') {
|
||||
bs_config_html = jsonToNestedTable(model.DecayingModel.parameters.base_score_config, [], ['table', 'table-condensed', 'table-bordered']);
|
||||
}
|
||||
var settings_html = '';
|
||||
if (!Array.isArray(model.DecayingModel.parameters.settings) && typeof model.DecayingModel.parameters.settings === 'object') {
|
||||
settings_html = jsonToNestedTable(model.DecayingModel.parameters.settings, [], ['table', 'table-condensed', 'table-bordered']);
|
||||
}
|
||||
var is_row_selected = $('#saveForm #save-model-button').data('modelid') == model.DecayingModel.id;
|
||||
return cells_html = [
|
||||
this._gen_td('<input type="checkbox" onchange="decayingTool.refreshSaveButton()" style="margin:0" ' + (is_row_selected ? 'checked' : 'disabled') + '></input>', 'DMCheckbox'),
|
||||
|
@ -939,6 +954,7 @@ ModelTable.prototype = {
|
|||
this._gen_td(model.DecayingModel.name, 'DMName'),
|
||||
this._gen_td_link('/organisations/view/'+model.DecayingModel.org_id, model.DecayingModel.org_id, 'DMOrg'),
|
||||
this._gen_td(model.DecayingModel.description, 'DMNDescription'),
|
||||
this._gen_td(model.DecayingModel.formula, 'DMFormula'),
|
||||
this._gen_td(model.DecayingModel.parameters.tau, 'DMParameterTau'),
|
||||
this._gen_td(model.DecayingModel.parameters.delta, 'DMParameterDelta'),
|
||||
this._gen_td(model.DecayingModel.parameters.threshold, 'DMParameterThreshold'),
|
||||
|
@ -948,6 +964,11 @@ ModelTable.prototype = {
|
|||
'DMParameterBasescoreConfig',
|
||||
{'basescoreconfig': btoa(JSON.stringify(model.DecayingModel.parameters.base_score_config))}
|
||||
),
|
||||
this._gen_td(
|
||||
settings_html,
|
||||
'DMSettings',
|
||||
{'basescoreconfig': btoa(JSON.stringify(model.DecayingModel.parameters.settings))}
|
||||
),
|
||||
this._gen_td(model.DecayingModel.attribute_types.length, 'DMNumType'),
|
||||
this._gen_td(model.DecayingModel.enabled ? '<i class="fa fa-check"></i>' : '<i class="fa fa-times"></i>', 'DMEnabled'),
|
||||
this._gen_td_buttons(model)
|
||||
|
|
Loading…
Reference in New Issue