Merge branch '2.4' of github.com:MISP/MISP into 2.4

pull/5687/head
mokaddem 2020-03-09 09:02:55 +01:00
commit 83542716e5
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
18 changed files with 889 additions and 46 deletions

View File

@ -76,7 +76,11 @@ class ACLComponent extends Component
'index' => array('*'),
'updateSettings' => array('*'),
'getEmptyWidget' => array('*'),
'renderWidget' => array('*')
'renderWidget' => array('*'),
'listTemplates' => array('*'),
'saveTemplate' => array('*'),
'export' => array('*'),
'import' => array('*')
),
'decayingModel' => array(
"update" => array(),

View File

@ -16,43 +16,65 @@ class DashboardsController extends AppController
'maxLimit' => 9999
);
public function index()
public function index($template_id = false)
{
$this->loadModel('UserSetting');
$params = array(
'conditions' => array(
'UserSetting.user_id' => $this->Auth->user('id'),
'UserSetting.setting' => 'dashboard'
)
);
$userSettings = $this->UserSetting->find('first', $params);
if (empty($template_id)) {
$params = array(
'conditions' => array(
'UserSetting.user_id' => $this->Auth->user('id'),
'UserSetting.setting' => 'dashboard'
)
);
$userSettings = $this->UserSetting->find('first', $params);
} else {
$dashboardTemplate = $this->Dashboard->getDashboardTemplate($this->Auth->user(), $template_id);
if (empty($dashboardTemplate)) {
throw new NotFoundException(__('Invalid dashboard template.'));
}
}
if (empty($userSettings) && empty($dashboardTemplate)) {
$dashboardTemplate = $this->Dashboard->getDashboardTemplate($this->Auth->user());
}
if (empty($userSettings)) {
if (empty($dashboardTemplate)) {
$value = array(
array(
'widget' => 'MispStatusWidget',
'config' => array(
),
'position' => array(
'x' => 0,
'y' => 0,
'width' => 2,
'height' => 2
)
)
);
} else {
$value = $dashboardTemplate['Dashboard']['value'];
if (!is_array($value)) {
$value = json_decode($value, true);
}
}
$userSettings = array(
'UserSetting' => array(
'setting' => 'dashboard',
'value' => array(
array(
'widget' => 'MispStatusWidget',
'config' => array(
),
'position' => array(
'x' => 0,
'y' => 0,
'width' => 2,
'height' => 2
)
)
)
'value' => $value
)
);
}
$widgets = array();
foreach ($userSettings['UserSetting']['value'] as $widget) {
$dashboardWidget = $this->Dashboard->loadWidget($this->Auth->user(), $widget['widget']);
$widget['width'] = $dashboardWidget->width;
$widget['height'] = $dashboardWidget->height;
$widget['title'] = $dashboardWidget->title;
$widgets[] = $widget;
try {
$dashboardWidget = $this->Dashboard->loadWidget($this->Auth->user(), $widget['widget']);
$widget['width'] = $dashboardWidget->width;
$widget['height'] = $dashboardWidget->height;
$widget['title'] = $dashboardWidget->title;
$widgets[] = $widget;
} catch (Exception $e) {
// continue, we just don't load the widget
}
}
$this->layout = 'dashboard';
$this->set('widgets', $widgets);
@ -162,4 +184,236 @@ class DashboardsController extends AppController
throw new MethodNotAllowedException(__('This endpoint can only be reached via POST requests.'));
}
}
public function import()
{
if ($this->request->is('post')) {
if (!empty($this->request->data['Dashboard'])) {
$this->request->data = json_decode($this->request->data['Dashboard']['value'], true);
}
if (!empty($this->request->data['UserSetting'])) {
$this->request->data = $this->request->data['UserSetting']['value'];
}
$result = $this->Dashboard->import($this->Auth->user(), $this->request->data);
if ($this->_isRest()) {
if ($result) {
return $this->RestResponse->saveSuccessResponse('Dashboard', 'import', false, false, __('Settings updated.'));
}
return $this->RestResponse->saveFailResponse('Dashboard', 'import', false, __('Settings could not be updated.'), $this->response->type());
} else {
if ($result) {
$this->Flash->success(__('Settings updated.'));
} else {
$this->Flash->error(__('Settings could not be updated.'));
}
$this->redirect($this->baseurl . '/dashboards');
}
}
$this->layout = false;
}
public function export()
{
$data = $this->Dashboard->export($this->Auth->user());
if ($this->_isRest()) {
return $this->RestResponse->viewData($data, $this->response->type());
} else {
$this->set('data', $data);
$this->layout = false;
}
}
public function saveTemplate($update = false)
{
if (!empty($update)) {
$conditions = array('Dashboard.id' => $update);
if (Validation::uuid($update)) {
$conditions = array('Dashboard.uuid' => $update);
}
$existingDashboard = $this->Dashboard->find('first', array(
'recursive' => -1,
'conditions' => $conditions
));
if (
empty($existingDashboard) ||
(!$this->_isSiteAdmin() && $existingDashboard['Dashboard']['user_id'] != $this->Auth->user('id'))
) {
throw new NotFoundException(__('Invalid dashboard template.'));
}
}
if ($this->request->is('post') || $this->request->is('put')) {
if (isset($this->request->data['Dashboard'])) {
$this->request->data = $this->request->data['Dashboard'];
}
$data = $this->request->data;
$data['value'] = $this->UserSetting->getSetting($this->Auth->user('id'), 'dashboard');
$result = $this->Dashboard->saveDashboardTemplate($this->Auth->user(), $data, $update);
if ($this->_isRest()) {
if ($result) {
return $this->RestResponse->saveSuccessResponse('Dashboard', 'saveDashboardTemplate', false, false, __('Dashboard template updated.'));
}
return $this->RestResponse->saveFailResponse('Dashboard', 'import', false, __('Dashboard template could not be updated.'), $this->response->type());
} else {
if ($result) {
$this->Flash->success(__('Dashboard template updated.'));
} else {
$this->Flash->error(__('Dashboard template could not be updated.'));
}
$this->redirect($this->baseurl . '/dashboards/listTemplates');
}
} else {
$this->layout = false;
}
$this->loadModel('User');
$permFlags = array(0 => __('Unrestricted'));
foreach ($this->User->Role->permFlags as $perm_flag => $perm_data) {
$permFlags[$perm_flag] = $perm_data['text'];
}
$options = array(
'org_id' => array_merge(
array(
0 => __('Unrestricted')
),
$this->User->Organisation->find('list', array(
'fields' => array(
'Organisation.id', 'Organisation.name'
),
'conditions' => array('Organisation.local' => 1)
))
),
'role_id' => array_merge(
array(
0 => __('Unrestricted')
),
$this->User->Role->find('list', array(
'fields' => array(
'Role.id', 'Role.name'
)
))
),
'role_perms' => $permFlags
);
if (!empty($update)) {
$this->request->data = $existingDashboard;
}
$this->set('options', $options);
}
public function listTemplates()
{
$conditions = array();
if (!$this->_isSiteAdmin()) {
$permission_flags = array();
foreach ($this->Auth->user('Role') as $perm => $value) {
if (strpos($perm, 'perm_') !== false && !empty($value)) {
$permission_flags[] = $perm;
}
}
$conditions['AND'] = array(
array(
'OR' => array(
'Dashboard.user_id' => $this->Auth->user('id'),
'AND' => array(
'Dashboard.selectable' => 1,
array(
'OR' => array(
array('Dashboard.restrict_to_org_id' => $this->Auth->user('org_id')),
array('Dashboard.restrict_to_org_id' => 0)
)
),
array(
'OR' => array(
array('Dashboard.restrict_to_role_id' => $this->Auth->user('role_id')),
array('Dashboard.restrict_to_role_id' => 0)
)
),
array(
'OR' => array(
array('Dashboard.restrict_to_permission_flag' => $permission_flags),
array('Dashboard.restrict_to_permission_flag' => 0)
)
)
)
)
)
);
}
if (!empty($this->passedArgs['value'])) {
$conditions['AND'][] = array(
'OR' => array(
'LOWER(Dashboard.name) LIKE' => '%' . strtolower(trim($this->passedArgs['value'])) . '%',
'LOWER(Dashboard.description) LIKE' => '%' . strtolower(trim($this->passedArgs['value'])) . '%',
'LOWER(Dashboard.uuid) LIKE' => strtolower(trim($this->passedArgs['value']))
)
);
}
$this->paginate['conditions'] = $conditions;
if ($this->_isRest()) {
$params = array(
'conditions' => $conditions,
'recursive' => -1
);
$paramsToPass = array('limit', 'page');
foreach ($paramsToPass as $p) {
if (!empty($this->passedArgs[$p])) {
$params[$p] = $this->passedArgs[$p];
}
}
$data = $this->Dashboard->find('all', $params);
foreach ($data as &$element) {
$element['Dashboard']['value'] = json_decode($element['Dashboard']['value'], true);
}
return $this->RestResponse->viewData(
$data,
$this->response->type()
);
} else {
$this->paginate['contain'] = array(
'User.id', 'User.email'
);
$data = $this->paginate();
foreach ($data as &$element) {
$element['Dashboard']['value'] = json_decode($element['Dashboard']['value'], true);
$widgets = array();
foreach ($element['Dashboard']['value'] as $val) {
$widgets[$val['widget']] = 1;
}
$element['Dashboard']['widgets'] = array_keys($widgets);
sort($element['Dashboard']['widgets']);
if ($element['Dashboard']['user_id'] != $this->Auth->user('id')) {
$element['User']['email'] = '';
}
}
$this->set('passedArgs', $this->passedArgs);
$this->set('data', $data);
}
}
public function deleteTemplate($id)
{
$conditions = array();
if (Validation::uuid($id)) {
$conditions['AND'][] = array('Dashboard.uuid' => $id);
} else {
$conditions['AND'][] = array('Dashboard.id' => $id);
}
if (!$this->_isSiteAdmin()) {
$conditions['AND'][] = array('Dashboard.user_id' => $this->Auth->user('id'));
}
$dashboard = $this->Dashboard->find('first', array(
'conditions' => $conditions,
'recursive' => -1
));
if (empty($dashboard)) {
throw new NotFoundException(__('Invalid dashboard template.'));
}
$this->Dashboard->delete($dashboard['Dashboard']['id']);
$message = __('Dashboard template removed.');
if ($this->_isRest()) {
return $this->RestResponse->saveSuccessResponse('Dashboard', 'delete', $id, false, $message);
} else {
$this->Flash->success($message);
$this->redirect($this->baseurl . '/dashboards/listTemplates');
}
}
}

View File

@ -0,0 +1,35 @@
<?php
class WhoamiWidget
{
public $title = 'Whoami';
public $render = 'SimpleList';
public $width = 2;
public $height = 2;
public $params = array();
public $description = 'Shows information about the currently logged in user.';
public $cacheLifetime = false;
public $autoRefreshDelay = 3;
public function handler($user, $options = array())
{
$this->Log = ClassRegistry::init('Log');
$entries = $this->Log->find('all', array(
'recursive' => -1,
'conditions' => array('action' => 'login', 'user_id' => $user['id']),
'order' => 'id desc',
'limit' => 5,
'fields' => array('created', 'ip')
));
foreach ($entries as &$entry) {
$entry = $entry['Log']['created'] . ' --- ' . (empty($entry['Log']['ip']) ? 'IP not logged' : $entry['Log']['ip']);
}
return array(
array('title' => 'Email', 'value' => $user['email']),
array('title' => 'Role', 'value' => $user['Role']['name']),
array('title' => 'Organisation', 'value' => $user['Organisation']['name']),
array('title' => 'IP', 'value' => empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? $_SERVER['REMOTE_ADDR'] : $_SERVER['HTTP_X_FORWARDED_FOR']),
array('title' => 'Last logins', 'value' => $entries)
);
}
}

View File

@ -77,7 +77,7 @@ class AppModel extends Model
27 => false, 28 => false, 29 => false, 30 => false, 31 => false, 32 => false,
33 => false, 34 => false, 35 => false, 36 => false, 37 => false, 38 => false,
39 => false, 40 => false, 41 => false, 42 => false, 43 => false, 44 => false,
45 => false, 46 => false, 47 => false, 48 => false
45 => false, 46 => false, 47 => false, 48 => false, 49 => false
);
public $advanced_updates_description = array(
@ -1327,6 +1327,28 @@ class AppModel extends Model
$this->__addIndex('taxonomy_predicates', 'numerical_value');
$this->__addIndex('taxonomy_entries', 'numerical_value');
break;
case 49:
$sqlArray[] = "CREATE TABLE IF NOT EXISTS dashboards (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uuid` varchar(40) COLLATE utf8_bin NOT NULL,
`name` varchar(191) NOT NULL,
`description` text,
`default` tinyint(1) NOT NULL DEFAULT 0,
`selectable` tinyint(1) NOT NULL DEFAULT 0,
`user_id` int(11) NOT NULL DEFAULT 0,
`restrict_to_org_id` int(11) NOT NULL DEFAULT 0,
`restrict_to_role_id` int(11) NOT NULL DEFAULT 0,
`restrict_to_permission_flag` varchar(191) NOT NULL DEFAULT '',
`value` text,
`timestamp` int(11) NOT NULL,
PRIMARY KEY (id),
INDEX `name` (`name`),
INDEX `uuid` (`uuid`),
INDEX `user_id` (`user_id`),
INDEX `restrict_to_org_id` (`restrict_to_org_id`),
INDEX `restrict_to_permission_flag` (`restrict_to_permission_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
break;
case 'fixNonEmptySharingGroupID':
$sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
$sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';

View File

@ -2,9 +2,34 @@
App::uses('AppModel', 'Model');
class Dashboard extends AppModel
{
public $useTable = false;
public $recursive = -1;
public function loadWidget($user, $name)
public $actsAs = array(
'Containable',
);
public $validate = array(
'user_id' => 'numeric',
'org_id' => 'numeric',
'role_id' => 'numeric',
'uuid' => array(
'uuid' => array(
'rule' => array('custom', '/^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/'),
'message' => 'Please provide a valid UUID'
),
)
);
public $belongsTo = array(
'User',
'Role',
'Organisation' => array(
'className' => 'Organisation',
'foreignKey' => 'org_id'
)
);
public function loadWidget($user, $name, $returnOnException = false)
{
$name = str_replace('/', '', $name);
if (file_exists(APP . 'Lib/Dashboard/' . $name . '.php')) {
@ -24,12 +49,18 @@ class Dashboard extends AppModel
}
}
if (!$found) {
if ($returnOnException) {
return false;
}
throw new NotFoundException(__('Invalid widget or widget not found.'));
}
}
$widget = new $name();
if (method_exists($widget, 'checkPermissions')) {
if (!$widget->checkPermissions($user)) {
if ($returnOnException) {
return false;
}
throw new NotFoundException(__('Invalid widget or widget not found.'));
}
}
@ -85,4 +116,167 @@ class Dashboard extends AppModel
);
return $widget;
}
public function import($user, $value, $targetUser = false)
{
$this->User = ClassRegistry::init('User');
if (empty($targetUser)) {
$targetUser = $user;
} else if (!is_array($targetUser)) {
$targetUser = $this->User->getAuthUser($targetUser);
}
if (empty($targetUser)) {
throw new NotFoundException(__('Invalid user.'));
}
$settingsToSave = array();
foreach ($value as $widgetConfig) {
$widget = $this->loadWidget($targetUser, $widgetConfig['widget'], true);
if (!empty($widget)) {
$settingsToSave[] = $widgetConfig;
}
}
$data = array(
'UserSetting' => array(
'user_id' => $targetUser['id'],
'setting' => 'dashboard',
'value' => $settingsToSave
)
);
return $this->User->UserSetting->setSetting($user, $data);
}
public function export($user)
{
$this->User = ClassRegistry::init('User');
$data = $this->User->UserSetting->find('first', array(
'recursive' => -1,
'conditions' => array(
'UserSetting.user_id' => $user['id'],
'UserSetting.setting' => 'dashboard'
)
));
return $data;
}
public function getDashboardTemplate($user, $dashboard_id = false)
{
if (empty($dashboard_id)) {
$conditions = array(
'Dashboard.default' => 1
);
} else {
if (Validation::uuid($dashboard_id)) {
$conditions = array(
'Dashboard.uuid' => $dashboard_id
);
} else {
$conditions = array(
'Dashboard.id' => $dashboard_id
);
}
}
$template = $this->find('first', array(
'recursive' => -1,
'conditions' => $conditions
));
if (empty($template)) {
return array();
}
if (empty($user['Role']['perm_site_admin'])) {
if (
$template['Dashboard']['user_id'] != $user['id'] &&
(
empty($template['Dashboard']['selectable']) ||
(
!empty($template['Dashboard']['restrict_to_org_id']) &&
$template['Dashboard']['restrict_to_org_id'] != $user['org_id']
) ||
(
!empty($template['Dashboard']['restrict_to_role_id']) &&
$template['Dashboard']['restrict_to_role_id'] != $user['role_id']
) ||
(
!empty($template['Dashboard']['restrict_to_permission_flag']) &&
empty($user['role'][$template['Dashboard']['restrict_to_permission_flag']])
)
)
) {
return array();
}
}
return $template;
}
public function saveDashboardTemplate($user, $settings, $update = false)
{
$this->User = ClassRegistry::init('User');
$data = $this->User->UserSetting->find('first', array(
'recursive' => -1,
'conditions' => array(
'UserSetting.user_id' => $user['id'],
'UserSetting.setting' => 'dashboard'
)
));
$editableFields = array(
'name',
'description',
'selectable',
'value'
);
if ($user['Role']['perm_site_admin']) {
$editableFields = array_merge(
$editableFields,
array(
'default',
'restrict_to_role_id',
'restrict_to_permission_flag',
'restrict_to_org_id'
)
);
}
if ($update) {
$existingDashboard = $this->getDashboardTemplate($user, $update);
if (empty($existingDashboard)) {
throw new NotFoundException(__('Invalid dashboard template.'));
}
$data = $existingDashboard['Dashboard'];
} else {
$this->create();
$data = array(
'user_id' => $user['id'],
'uuid' => CakeText::uuid()
);
if (empty($user['role']['perm_site_admin'])) {
$data['restrict_to_org_id'] = $user['org_id'];
}
}
foreach ($editableFields as $editable) {
if (isset($settings[$editable])) {
$data[$editable] = $settings[$editable];
}
}
$data['timestamp'] = time();
if (is_array($data['value'])) {
$data['value'] = json_encode($data['value']);
}
if (!empty($data['default'])) {
$this->__unsetPreviousDefault();
}
return $this->save(array('Dashboard' => $data));
}
private function __unsetPreviousDefault()
{
$currentDefault = $this->find('first', array(
'recursive' => -1,
'conditions' => array(
'Dashboard.default' => 1
)
));
if (!empty($currentDefault)) {
$currentDefault['Dashboard']['default'] = 0;
$this->save($currentDefault);
}
return true;
}
}

View File

@ -349,4 +349,19 @@ class UserSetting extends AppModel
$result = $this->save(array('UserSetting' => $userSetting));
return true;
}
public function getSetting($user_id, $setting)
{
$setting = $this->find('first', array(
'recursive' => -1,
'conditions' => array(
'UserSetting.user_id' => $user_id,
'UserSetting.setting' => $setting
)
));
if (empty($setting)) {
return array();
}
return $setting['UserSetting']['value'];
}
}

View File

@ -0,0 +1,15 @@
<?php
$data = array(
'title' => __('Export Dashboard Settings'),
'content' => array(
array(
'paragraph' => __('Simply copy and share your dashboard settings below. Make sure that you sanitise it so that you do not share anything sensitive. Simply click on the JSON below to select it.')
),
array(
'title' => __('Dashboard settings'),
'code' => json_encode($data, JSON_PRETTY_PRINT)
)
)
);
echo $this->element('genericElements/infoModal', array('data' => $data));
?>

View File

@ -0,0 +1,25 @@
<?php
echo $this->element('genericElements/Form/genericForm', array(
'form' => $this->Form,
'url' => 'updateSettings',
'data' => array(
'title' => __('Import Dashboard Configuration'),
'model' => 'Dashboard',
'fields' => array(
array(
'field'=> 'value',
'type' => 'textarea',
'class' => 'input span6',
'div' => 'input clear',
'label' => __('Config'),
'default' => empty($data['config']) ? '' : json_encode($data['config'], JSON_PRETTY_PRINT)
)
),
'submit' => array(
'action' => 'import',
'ajaxSubmit' => "$('#DashboardImportForm').submit();"
),
'description' => __('Import a configuration JSON as exported from another MISP instance.')
)
));
?>

View File

@ -0,0 +1,107 @@
<?php
echo '<div class="index">';
echo $this->element('/genericElements/IndexTable/index_table', array(
'data' => array(
'data' => $data,
'top_bar' => array(
'children' => array(
array(
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'value'
)
)
),
'fields' => array(
array(
'name' => __('Id'),
'sort' => 'id',
'class' => 'short',
'data_path' => 'Dashboard.id',
),
array(
'name' => __('UUID'),
'sort' => 'uuid',
'class' => 'short',
'data_path' => 'Dashboard.uuid',
),
array(
'name' => __('Owner'),
'sort' => 'User.email',
'class' => 'short',
'data_path' => 'User.email',
),
array(
'name' => __('Name'),
'sort' => 'name',
'class' => 'short',
'data_path' => 'Dashboard.name',
),
array(
'name' => __('Description'),
'data_path' => 'Dashboard.description',
),
array(
'name' => __('Widgets Used'),
'data_path' => 'Dashboard.widgets',
'element' => 'list'
),
array(
'name' => __('Selectable'),
'element' => 'boolean',
'class' => 'short',
'data_path' => 'Dashboard.selectable',
),
array(
'name' => __('Default'),
'element' => 'boolean',
'class' => 'short',
'data_path' => 'Dashboard.default',
)
),
'title' => __('Dashboard Templates Index'),
'description' => __('Users can create and save dashboard templates. Additionally, administrators can create selectable templates for the community and select a default to be used by new users.'),
'actions' => array(
array(
'url' => '/dashboards/index',
'url_params_data_paths' => array(
'Dashboard.uuid'
),
'title' => 'Load and set template',
'icon' => 'eye'
),
array(
'onclick' => 'openGenericModal(\'' . $baseurl . '/dashboards/saveTemplate/[onclick_params_data_path]\');',
'onclick_params_data_path' => 'Dashboard.uuid',
'icon' => 'edit'
),
array(
'url' => '/dashboards/deleteTemplate',
'url_params_data_paths' => array(
'Dashboard.uuid'
),
'postLink' => 1,
'postLinkConfirm' => __('Are you sure you want to remove this dashboard template?'),
'icon' => 'trash'
)
)
)
));
echo '</div>';
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'dashboard', 'menuItem' => 'dashboardTemplateIndex'));
?>
<script type="text/javascript">
var passedArgsArray = <?php echo json_encode($passedArgs); ?>;
$(document).ready(function() {
$('#quickFilterButton').click(function() {
runIndexQuickFilter();
});
$('#quickFilterField').on('keypress', function (e) {
if(e.which === 13) {
runIndexQuickFilter();
}
});
});
</script>

View File

@ -0,0 +1,70 @@
<?php
echo $this->element('genericElements/Form/genericForm', array(
'form' => $this->Form,
'url' => 'saveDashboardTemplate',
'data' => array(
'title' => __('Save Dashboard Template'),
'model' => 'Dashboard',
'fields' => array(
array(
'field'=> 'name',
'type' => 'text',
'class' => 'input span6',
'div' => 'input clear',
'label' => __('Template Name')
),
array(
'field'=> 'description',
'type' => 'textarea',
'class' => 'input span6',
'div' => 'input clear',
'label' => __('Description')
),
array(
'field' => 'restrict_to_org_id',
'options' => $options['org_id'],
'class' => 'input span6',
'div' => 'input clear',
'label' => __('Restrict to organisation'),
'requirements' => $isSiteAdmin
),
array(
'field' => 'restrict_to_role_id',
'options' => $options['role_id'],
'class' => 'input span6',
'div' => 'input clear',
'label' => __('Restrict to role'),
'requirements' => $isSiteAdmin
),
array(
'field' => 'restrict_to_permission_flag',
'options' => $options['role_perms'],
'class' => 'input span6',
'div' => 'input clear',
'label' => __('Restrict to role permission flag'),
'requirements' => $isSiteAdmin
),
array(
'field' => 'selectable',
'type' => 'checkbox',
'class' => 'input',
'div' => 'input',
'label' => __('Selectable')
),
array(
'field' => 'default',
'type' => 'checkbox',
'class' => 'input',
'div' => 'input',
'label' => __('Default'),
'requirements' => $isSiteAdmin
),
),
'submit' => array(
'action' => 'import',
'ajaxSubmit' => "$('#DashboardSaveTemplateForm').submit();"
),
'description' => __('Save your current dashboard state as a template for others to reuse.')
)
));
?>

View File

@ -32,14 +32,14 @@
});
var container_<?= $randomNumber ?> = $('#world-map-<?= $randomNumber ?>').parent().parent();
function resizeDashboardWorldMap() {
var width = container_<?= $randomNumber ?>.width();
var height = container_<?= $randomNumber ?>.height() - 60;
$('#world-map-<?= $randomNumber ?>').css('width', width + 'px');
$('#world-map-<?= $randomNumber ?>').css('height', height + 'px');
$('#world-map-<?= $randomNumber ?>').vectorMap('get','mapObject').updateSize();
function resizeDashboardWorldMap(id) {
var width = eval('container_' + id + '.width()');
var height = eval('container_' + id + '.height() - 60');
$('#world-map-' + id).css('width', width + 'px');
$('#world-map-' + id).css('height', height + 'px');
$('#world-map-' + id).vectorMap('get','mapObject').updateSize();
}
$(document).ready(function() {
resizeDashboardWorldMap();
resizeDashboardWorldMap(<?= $randomNumber ?>);
});
</script>

View File

@ -6,7 +6,8 @@
* - model: The model used to create the form (such as Attribute, Event)
* - fields: an array with each element generating an input field
* - field is the actual field name (such as org_id, name, etc) which is required
* - optional fields: default, type, options, placeholder, label - these are passed directly to $this->Form->input()
* - optional fields: default, type, options, placeholder, label - these are passed directly to $this->Form->input(),
* - requirements: boolean, if false is passed the field is skipped
* - metafields: fields that are outside of the scope of the form itself
- use these to define dynamic form fields, or anything that will feed into the regular fields via JS population
* - submit: The submit button itself. By default it will simply submit to the form as defined via the 'model' field
@ -22,6 +23,9 @@
$formCreate = $this->Form->create($modelForForm);
if (!empty($data['fields'])) {
foreach ($data['fields'] as $fieldData) {
if (isset($fieldData['requirements']) && !$fieldData['requirements']) {
continue;
}
if (is_array($fieldData)) {
if (empty($fieldData['label'])) {
$fieldData['label'] = Inflector::humanize($fieldData['field']);

View File

@ -8,7 +8,9 @@
* - title: title of the action. Automatically generates aria labels too
* - postLink: convert the button into a POST request
* - postLinkConfirm: As the user to confirm the POST before submission with the given message
* - onClick: custom onClick action instead of a simple GET/POST request
* - onclick: custom onclick action instead of a simple GET/POST request
* - onclick_params_data_path: pass a data path param to the onclick field. requires [onclick_params_data_path] in the onclick field
* as a needle for replacement
* - icon: FA icon (added using the helper, knowing the fa domain is not needed, just add the short name such as "edit")
* - requirement evaluates to true/false
* - complex_requirement - add complex requirements via lambda functions:
@ -55,12 +57,20 @@
empty($action['postLinkConfirm'])? '' : $action['postLinkConfirm']
);
} else {
if (!empty($action['onclick']) && !empty($action['onclick_params_data_path'])) {
$action['onclick'] = str_replace(
'[onclick_params_data_path]',
h(Hash::extract($row, $action['onclick_params_data_path'])[0]),
$action['onclick']
);
}
echo sprintf(
'<a href="%s" title="%s" aria-label="%s" %s><i class="black %s"></i></a> ',
$url,
empty($action['title']) ? '' : h($action['title']),
empty($action['title']) ? '' : h($action['title']),
empty($action['onclick']) ? '' : sprintf('onclick="%s"', $action['onclick']),
empty($action['onclick']) ? '' : sprintf('onClick="%s"', $action['onclick']),
$this->FontAwesome->getClass($action['icon'])
);
}

View File

@ -0,0 +1,8 @@
<?php
$data = Hash::extract($row, $field['data_path']);
foreach ($data as &$element) {
$element = h($element);
}
$data = implode('<br />', $data);
echo $data;
?>

View File

@ -10,7 +10,8 @@
'field' => $field,
'row' => $row,
'column' => $column,
'data_path' => empty($field['data_path']) ? '' : $field['data_path'], 'k' => $k
'data_path' => empty($field['data_path']) ? '' : $field['data_path'],
'k' => $k
)
);
}

View File

@ -17,6 +17,38 @@
'params' => array($baseurl . '/dashboards/getForm/add')
),
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'dashboardImport',
'url' => '#',
'text' => __('Import Config JSON'),
'onClick' => array(
'function' => 'openGenericModal',
'params' => array($baseurl . '/dashboards/import')
),
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'dashboardExport',
'url' => '#',
'text' => __('Export Config JSON'),
'onClick' => array(
'function' => 'openGenericModal',
'params' => array($baseurl . '/dashboards/export')
),
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'dashboardSave',
'url' => '#',
'text' => __('Save Dashboard Config'),
'onClick' => array(
'function' => 'openGenericModal',
'params' => array($baseurl . '/dashboards/saveTemplate')
),
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'dashboardTemplateIndex',
'url' => '/dashboards/listTemplates',
'text' => __('List Dashboard Templates')
));
break;
case 'event':
$dataEventId = isset($event['Event']['id']) ? h($event['Event']['id']) : 0;

View File

@ -0,0 +1,43 @@
<?php
/*
* Generic modal builder
*
* Simply pass a JSON with the following keys set:
* - title: A title for the modal
* - content: array of ordered content elements
* - title: A label for a content element (optional)
* - paragraph: Text to be displayed (optional)
* - html: HTML to be displayed directly (optional)
* - code: Code snipet to be displayed - copy pastable (optional)
*/
$contents = '';
foreach ($data['content'] as $content) {
$contents .= sprintf(
'%s%s%s%s',
empty($content['title']) ? '' : sprintf('<h4>%s</h4>', h($content['title'])),
empty($content['paragraph']) ? '' : sprintf('<p>%s</p>', h($content['paragraph'])),
empty($content['html']) ? '' : sprintf('<div class="modalContentHtmlDiv">%s</div>', $content['html']),
empty($content['code']) ? '' : sprintf('<pre class="quickSelect" onClick="quickSelect(this);">%s</pre>', h($content['code']))
);
}
$action = $this->request->params['action'];
$controller = $this->request->params['controller'];
echo sprintf(
'<div id="genericModal" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="genericModalLabel" aria-hidden="true">%s%s%s</div>',
sprintf(
'<div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button><h3 id="genericModalLabel">%s</h3></div>',
empty($data['title']) ?
h(Inflector::humanize($action)) . ' ' . h(Inflector::singularize(Inflector::humanize($controller))) :
h($data['title'])
),
sprintf(
'<div class="modal-body modal-body-long">%s</div>',
$contents
),
sprintf(
'<div class="modal-footer"><button class="btn" data-dismiss="modal" aria-hidden="true" onClick="%s">%s</button></div>',
'cancelPopoverForm();',
__('Cancel')
)
);
?>

View File

@ -4459,6 +4459,14 @@ function checkNoticeList(type) {
}
function quickSelect(target) {
var range = document.createRange();
var selection = window.getSelection();
range.selectNodeContents(target);
selection.removeAllRanges();
selection.addRange(range);
}
$(document).ready(function() {
$('#quickFilterField').bind("enterKey",function(e){
$('#quickFilterButton').trigger("click");
@ -4499,11 +4507,7 @@ $(document).ready(function() {
// clicking on an element with this class will select all of its contents in a
// single click
$('.quickSelect').click(function() {
var range = document.createRange();
var selection = window.getSelection();
range.selectNodeContents(this);
selection.removeAllRanges();
selection.addRange(range);
quickSelect(this);
});
$(".cortex-json").click(function() {
var cortex_data = $(this).data('cortex-json');