diff --git a/.gitignore b/.gitignore index afe719952..69dc56f31 100755 --- a/.gitignore +++ b/.gitignore @@ -103,3 +103,4 @@ tools/mkdocs .ropeproject/ vagrant/.vagrant/ vagrant/*.log +/app/Lib/Dashboard/Custom/* diff --git a/app/Controller/AppController.php b/app/Controller/AppController.php index 2a3dfb7ce..08c52acfd 100755 --- a/app/Controller/AppController.php +++ b/app/Controller/AppController.php @@ -46,7 +46,7 @@ class AppController extends Controller public $helpers = array('Utility', 'OrgImg', 'FontAwesome', 'UserName'); - private $__queryVersion = '98'; + private $__queryVersion = '99'; public $pyMispVersion = '2.4.122'; public $phpmin = '7.2'; public $phprec = '7.4'; @@ -510,6 +510,18 @@ class AppController extends Controller } } $this->components['RestResponse']['sql_dump'] = $this->sql_dump; + $this->loadModel('UserSetting'); + $homepage = $this->UserSetting->find('first', array( + 'recursive' => -1, + 'conditions' => array( + 'UserSetting.user_id' => $this->Auth->user('id'), + 'UserSetting.setting' => 'homepage' + ), + 'contain' => array('User.id', 'User.org_id') + )); + if (!empty($homepage)) { + $this->set('homepage', $homepage['UserSetting']['value']); + } } private function __rateLimitCheck() diff --git a/app/Controller/Component/ACLComponent.php b/app/Controller/Component/ACLComponent.php index e6b1520bb..9de4c369d 100644 --- a/app/Controller/Component/ACLComponent.php +++ b/app/Controller/Component/ACLComponent.php @@ -71,6 +71,13 @@ class ACLComponent extends Component 'view' => array('*'), 'viewPicture' => array('*'), ), + 'dashboards' => array( + 'getForm' => array('*'), + 'index' => array('*'), + 'updateSettings' => array('*'), + 'getEmptyWidget' => array('*'), + 'renderWidget' => array('*') + ), 'decayingModel' => array( "update" => array(), "export" => array('*'), @@ -575,7 +582,8 @@ class ACLComponent extends Component 'view' => array('*'), 'setSetting' => array('*'), 'getSetting' => array('*'), - 'delete' => array('*') + 'delete' => array('*'), + 'setHomePage' => array('*') ), 'warninglists' => array( 'checkValue' => array('perm_auth'), diff --git a/app/Controller/DashboardsController.php b/app/Controller/DashboardsController.php new file mode 100644 index 000000000..5744ed825 --- /dev/null +++ b/app/Controller/DashboardsController.php @@ -0,0 +1,155 @@ +Security->unlockedActions = array('renderWidget', 'updateSettings', 'getForm'); + } + + public $paginate = array( + 'limit' => 60, + 'maxLimit' => 9999 + ); + + public function index() + { + $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($userSettings)) { + $userSettings = array( + 'UserSetting' => array( + 'setting' => 'dashboard', + 'value' => array( + array( + 'widget' => 'MispStatusWidget', + 'config' => array( + ), + 'position' => array( + 'x' => 0, + 'y' => 0, + 'width' => 2, + 'height' => 2 + ) + ) + ) + ) + ); + } + $widgets = array(); + foreach ($userSettings['UserSetting']['value'] as $widget) { + $dashboardWidget = $this->Dashboard->loadWidget($widget['widget']); + $widget['width'] = $dashboardWidget->width; + $widget['height'] = $dashboardWidget->height; + $widget['title'] = $dashboardWidget->title; + $widgets[] = $widget; + } + $this->layout = 'dashboard'; + $this->set('widgets', $widgets); + } + + public function getForm($action = 'edit') + { + if ($this->request->is('post') || $this->request->is('put')) { + $data = $this->request->data; + if ($action === 'edit' && !isset($data['widget'])) { + throw new InvalidArgumentException(__('No widget name passed.')); + } + if (empty($data['config'])) { + $data['config'] = ''; + } + if ($action === 'add') { + $data['widget_options'] = $this->Dashboard->loadAllWidgets(); + } else { + $dashboardWidget = $this->Dashboard->loadWidget($data['widget']); + $data['description'] = empty($dashboardWidget->description) ? '' : $dashboardWidget->description; + $data['params'] = empty($dashboardWidget->params) ? array() : $dashboardWidget->params; + $data['params'] = array_merge(array('alias' => __('Alias to use as the title of the widget')), $data['params']); + } + $this->set('data', $data); + $this->layout = false; + $this->render($action); + } + } + + public function updateSettings() + { + if ($this->request->is('post')) { + $this->UserSetting = ClassRegistry::init('UserSetting'); + if (!isset($this->request->data['value'])) { + throw new InvalidArgumentException(__('No setting data found.')); + } + $data = array( + 'UserSetting' => array( + 'user_id' => $this->Auth->user('id'), + 'setting' => 'dashboard', + 'value' => $this->request->data['value'] + ) + ); + $result = $this->UserSetting->setSetting($this->Auth->user(), $data); + if ($result) { + return $this->RestResponse->saveSuccessResponse('Dashboard', 'updateSettings', false, false, __('Settings updated.')); + } + return $this->RestResponse->saveFailResponse('Dashboard', 'updateSettings', false, $this->UserSetting->validationErrors, $this->response->type()); + } + } + + public function getEmptyWidget($widget, $k = 1) + { + $dashboardWidget = $this->Dashboard->loadWidget($widget); + if (empty($dashboardWidget)) { + throw new NotFoundException(__('Invalid widget.')); + } + $this->layout = false; + $widget = array( + 'config' => isset($dashboardWidget->config) ? $dashboardWidget->height : '', + 'title' => $dashboardWidget->title, + 'alias' => isset($dashboardWidget->alias) ? $dashboardWidget->alias : $dashboardWidget->title, + 'widget' => $widget + ); + $this->set('k', $k); + $this->set('widget', $widget); + } + + public function renderWidget($force = false) + { + if ($this->request->is('post')) { + if (empty($this->request->data['data'])) { + $this->request->data = array('data' => $this->request->data); + + } + if (empty($this->request->data['data'])) { + throw new MethodNotAllowedException(__('You need to specify the widget to use along with the configuration.')); + } + $value = $this->request->data['data']; + $dashboardWidget = $this->Dashboard->loadWidget($value['widget']); + $this->layout = false; + $this->set('title', $dashboardWidget->title); + $redis = $this->Dashboard->setupRedis(); + $org_scope = $this->_isSiteAdmin() ? 0 : $this->Auth->user('org_id'); + $lookup_hash = hash('sha256', $value['widget'] . $value['config']); + $data = $redis->get('misp:dashboard:' . $org_scope . ':' . $lookup_hash); + if (empty($data)) { + $data = $dashboardWidget->handler($this->Auth->user(), json_decode($value['config'], true)); + $redis->set('misp:dashboard:' . $org_scope . ':' . $lookup_hash, json_encode(array('data' => $data))); + $redis->expire('misp:dashboard:' . $org_scope . ':' . $lookup_hash, 60); + } else { + $data = json_decode($data, true)['data']; + } + $this->set('data', $data); + $this->render('/Dashboards/Widgets/' . $dashboardWidget->render); + } else { + throw new MethodNotAllowedException(__('This endpoint can only be reached via POST requests.')); + } + } +} diff --git a/app/Controller/UserSettingsController.php b/app/Controller/UserSettingsController.php index ed4faacf4..a3485d919 100644 --- a/app/Controller/UserSettingsController.php +++ b/app/Controller/UserSettingsController.php @@ -25,6 +25,12 @@ class UserSettingsController extends AppController ) ); + public function beforeFilter() + { + parent::beforeFilter(); + $this->Security->unlockedActions = array('setHomePage'); + } + public function index() { $filterData = array( @@ -172,49 +178,7 @@ class UserSettingsController extends AppController $userSetting = array( 'user_id' => $this->Auth->user('id') ); - if (!empty($this->request->data['UserSetting']['user_id']) && is_numeric($this->request->data['UserSetting']['user_id'])) { - $user = $this->UserSetting->User->find('first', array( - 'recursive' => -1, - 'conditions' => array('User.id' => $this->request->data['UserSetting']['user_id']), - 'fields' => array('User.org_id') - )); - if ( - $this->_isSiteAdmin() || - ($this->_isAdmin() && ($user['User']['org_id'] == $this->Auth->user('org_id'))) - ) { - $userSetting['user_id'] = $this->request->data['UserSetting']['user_id']; - } - } - if (empty($this->request->data['UserSetting']['setting']) || !isset($this->request->data['UserSetting']['setting'])) { - throw new MethodNotAllowedException(__('This endpoint expects both a setting and a value to be set.')); - } - if (!$this->UserSetting->checkSettingValidity($this->request->data['UserSetting']['setting'])) { - throw new MethodNotAllowedException(__('Invalid setting.')); - } - $settingPermCheck = $this->UserSetting->checkSettingAccess($this->Auth->user(), $this->request->data['UserSetting']['setting']); - if ($settingPermCheck !== true) { - throw new MethodNotAllowedException(__('This setting is restricted and requires the following permission(s): %s', $settingPermCheck)); - } - $userSetting['setting'] = $this->request->data['UserSetting']['setting']; - if ($this->request->data['UserSetting']['value'] !== '') { - $userSetting['value'] = json_encode(json_decode($this->request->data['UserSetting']['value'], true)); - } else { - $userSetting['value'] = ''; - } - $existingSetting = $this->UserSetting->find('first', array( - 'recursive' => -1, - 'conditions' => array( - 'UserSetting.user_id' => $userSetting['user_id'], - 'UserSetting.setting' => $userSetting['setting'] - ) - )); - if (empty($existingSetting)) { - $this->UserSetting->create(); - } else { - $userSetting['id'] = $existingSetting['UserSetting']['id']; - } - // save the setting - $result = $this->UserSetting->save(array('UserSetting' => $userSetting)); + $result = $this->UserSetting->setSetting($this->Auth->user(), $this->request->data); if ($result) { // if we've managed to save our setting if ($this->_isRest()) { @@ -358,4 +322,26 @@ class UserSettingsController extends AppController throw new MethodNotAllowedException(__('Expecting POST or DELETE request.')); } } + + public function setHomePage() + { + if (!$this->request->is('post')) { + throw new MethodNotAllowedException(__('This endpoint only aaccepts POST requests.')); + } + if (empty($this->request->data['path'])) { + $this->request->data = array('path' => $this->request->data); + } + if (empty($this->request->data['path'])) { + throw new InvalidArgumentException(__('No path POSTed.')); + } + $setting = array( + 'UserSetting' => array( + 'user_id' => $this->Auth->user('id'), + 'setting' => 'homepage', + 'value' => json_encode(array('path' => $this->request->data['path'])) + ) + ); + $result = $this->UserSetting->setSetting($this->Auth->user(), $setting); + return $this->RestResponse->saveSuccessResponse('UserSettings', 'setHomePage', false, $this->response->type(), 'Homepage set to ' . $this->request->data['path']); + } } diff --git a/app/Controller/UsersController.php b/app/Controller/UsersController.php index f66d80c57..abc74ad90 100644 --- a/app/Controller/UsersController.php +++ b/app/Controller/UsersController.php @@ -1195,7 +1195,19 @@ class UsersController extends AppController // Events list $url = $this->Session->consume('pre_login_requested_url'); if (empty($url)) { - $url = array('controller' => 'events', 'action' => 'index'); + $homepage = $this->User->UserSetting->find('first', array( + 'recursive' => -1, + 'conditions' => array( + 'UserSetting.user_id' => $this->Auth->user('id'), + 'UserSetting.setting' => 'homepage' + ), + 'contain' => array('User.id', 'User.org_id') + )); + if (!empty($homepage)) { + $url = $homepage['UserSetting']['value']['path']; + } else { + $url = array('controller' => 'events', 'action' => 'index'); + } } $this->redirect($url); } diff --git a/app/Lib/Dashboard/MispStatusWidget.php b/app/Lib/Dashboard/MispStatusWidget.php new file mode 100644 index 000000000..0454bc08e --- /dev/null +++ b/app/Lib/Dashboard/MispStatusWidget.php @@ -0,0 +1,73 @@ +Event = ClassRegistry::init('Event'); + // the last login in the session is not updated after the login - only in the db, so let's fetch it. + $lastLogin = $user['last_login']; + $data = array(); + $data[] = array( + 'title' => __('Events modified'), + 'value' => count($this->Event->fetchEventIds($user, false, false, false, true, $lastLogin)), + 'html' => sprintf( + ' (%s)', + Configure::read('MISP.baseurl') . '/events/index/timestamp:' . (time() - 86400), + 'View' + ) + ); + $data[] = array( + 'title' => __('Events published'), + 'value' => count($this->Event->fetchEventIds($user, false, false, false, true, false, $lastLogin)), + 'html' => sprintf( + ' (%s)', + Configure::read('MISP.baseurl') . '/events/index/published:1/timestamp:' . (time() - 86400), + 'View' + ) + ); + $notifications = $this->Event->populateNotifications($user); + if (!empty($notifications['proposalCount'])) { + $data[] = array( + 'title' => __('Pending proposals'), + 'value' => $notifications['proposalCount'], + 'html' => sprintf( + ' (%s)', + Configure::read('MISP.baseurl') . '/shadow_attributes/index/all:0', + 'View' + ) + ); + } + if (!empty($notifications['proposalEventCount'])) { + $data[] = array( + 'title' => __('Events with proposals'), + 'value' => $notifications['proposalEventCount'], + 'html' => sprintf( + ' (%s)', + Configure::read('MISP.baseurl') . '/events/proposalEventIndex', + 'View' + ) + ); + } + if (!empty($notifications['delegationCount'])) { + $data[] = array( + 'title' => __('Delegation requests'), + 'value' => $notifications['delegationCount'], + 'html' => sprintf( + ' (%s)', + Configure::read('MISP.baseurl') . '/event_delegations/index/context:pending', + 'View' + ) + ); + } + return $data; + } +} diff --git a/app/Lib/Dashboard/TrendingTagsWidget.php b/app/Lib/Dashboard/TrendingTagsWidget.php new file mode 100644 index 000000000..d329d2021 --- /dev/null +++ b/app/Lib/Dashboard/TrendingTagsWidget.php @@ -0,0 +1,77 @@ + 'The time window, going back in seconds, that should be included.', + 'exclude' => 'List of substrings to exclude tags by - for example "sofacy" would exclude any tag containing sofacy.', + 'include' => 'List of substrings to include tags by - for example "sofacy" would include any tag containing sofacy.' + ); + public $placeholder = +'{ + "time_window": "86400", + "exclude": ["tlp:", "pap:"], + "include": ["misp-galaxy:", "my-internal-taxonomy"] +}'; + public $description = 'Widget showing the trending tags over the past x seconds, along with the possibility to include/exclude tags.'; + + public function handler($user, $options = array()) + { + $this->Event = ClassRegistry::init('Event'); + $params = array( + 'metadata' => 1, + 'timestamp' => time() - (empty($options['time_window']) ? 8640000 : $options['time_window']) + ); + $eventIds = $this->Event->filterEventIds($user, $params); + $params['eventid'] = $eventIds; + $events = array(); + if (!empty($eventIds)) { + $events = $this->Event->fetchEvent($user, $params); + } + $tags = array(); + $tagColours = array(); + $rules['exclusions'] = empty($options['exclude']) ? array() : $options['exclude']; + $rules['inclusions'] = empty($options['exclude']) ? array() : $options['exclude']; + foreach ($events as $event) { + foreach ($event['EventTag'] as $et) { + if ($this->checkTag($options, $et['Tag']['name'])) { + if (empty($tags[$et['Tag']['name']])) { + $tags[$et['Tag']['name']] = 1; + $tagColours[$et['Tag']['name']] = $et['Tag']['colour']; + } else { + $tags[$et['Tag']['name']] += 1; + } + } + } + } + arsort($tags); + $data['data'] = array_slice($tags, 0, 10); + $data['colours'] = $tagColours; + return $data; + } + + private function checkTag($options, $tag) + { + if (!empty($options['exclude'])) { + foreach ($options['exclude'] as $exclude) { + if (strpos($tag, $exclude) !== false) { + return false; + } + } + } + if (!empty($options['include'])) { + foreach ($options['include'] as $include) { + if (strpos($tag, $include) !== false) { + return true; + } + } + return false; + } else { + return true; + } + } +} diff --git a/app/Model/Dashboard.php b/app/Model/Dashboard.php new file mode 100644 index 000000000..a5f4d0cf4 --- /dev/null +++ b/app/Model/Dashboard.php @@ -0,0 +1,50 @@ +find('.*Widget\.php'); + $customWidgetFiles = $customdir->find('.*Widget\.php'); + $widgets = array(); + foreach ($widgetFiles as $widgetFile) { + $className = substr($widgetFile, 0, strlen($widgetFile) -4); + $widgets[$className] = $this->__extractMeta($className, false); + } + return $widgets; + } + + private function __extractMeta($className, $custom) + { + App::uses($className, 'Dashboard' . $custom ? '/Custom' : ''); + $widgetClass = new $className(); + $widget = array( + 'widget' => $className, + 'title' => $widgetClass->title, + 'render' => $widgetClass->render, + 'params' => empty($widgetClass->params) ? array() : $widgetClass->params, + 'description' => empty($widgetClass->description) ? $widgetClass->title : $widgetClass->description, + 'height' => empty($widgetClass->height) ? 1 : $widgetClass->height, + 'width' => empty($widgetClass->width) ? 1 : $widgetClass->width, + 'placeholder' => empty($widgetClass->placeholder) ? '' : $widgetClass->placeholder + ); + return $widget; + } +} diff --git a/app/Model/Event.php b/app/Model/Event.php index 98d713970..79b8c7129 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -1717,6 +1717,9 @@ class Event extends AppModel 'recursive' => -1, 'fields' => $fields ); + if (isset($params['order'])) { + $find_params['order'] = $params['order']; + } if (isset($params['limit'])) { // Get the count (but not the actual data) of results for paginators $result_count = $this->find('count', $find_params); diff --git a/app/Model/UserSetting.php b/app/Model/UserSetting.php index c472185aa..433fbd685 100644 --- a/app/Model/UserSetting.php +++ b/app/Model/UserSetting.php @@ -49,6 +49,24 @@ class UserSetting extends AppModel 'dashboard_access' => array( 'placeholder' => 1, 'restricted' => 'perm_site_admin' + ), + 'dashboard' => array( + 'placeholder' => array( + array( + 'widget' => 'MispStatusWidget', + 'config' => array( + ), + 'position' => array( + 'x' => 0, + 'y' => 0, + 'width' => 2, + 'height' => 2 + ) + ) + ) + ), + 'homepage' => array( + 'path' => '/events/index' ) ); @@ -279,4 +297,56 @@ class UserSetting extends AppModel } return false; } + + public function setSetting($user, &$data) + { + $userSetting = array(); + if (!empty($data['UserSetting']['user_id']) && is_numeric($data['UserSetting']['user_id'])) { + $user_to_edit = $this->User->find('first', array( + 'recursive' => -1, + 'conditions' => array('User.id' => $data['UserSetting']['user_id']), + 'fields' => array('User.org_id') + )); + if ( + !empty($user['Role']['perm_site_admin']) || + (!empty($user['Role']['perm_admin']) && ($user_to_edit['User']['org_id'] == $user['org_id'])) + ) { + $userSetting['user_id'] = $data['UserSetting']['user_id']; + } + } + if (empty($userSetting['user_id'])) { + $userSetting['user_id'] = $user['id']; + } + if (empty($data['UserSetting']['setting']) || !isset($data['UserSetting']['setting'])) { + throw new MethodNotAllowedException(__('This endpoint expects both a setting and a value to be set.')); + } + if (!$this->checkSettingValidity($data['UserSetting']['setting'])) { + throw new MethodNotAllowedException(__('Invalid setting.')); + } + $settingPermCheck = $this->checkSettingAccess($user, $data['UserSetting']['setting']); + if ($settingPermCheck !== true) { + throw new MethodNotAllowedException(__('This setting is restricted and requires the following permission(s): %s', $settingPermCheck)); + } + $userSetting['setting'] = $data['UserSetting']['setting']; + if ($data['UserSetting']['value'] !== '') { + $userSetting['value'] = $data['UserSetting']['value']; + } else { + $userSetting['value'] = ''; + } + $existingSetting = $this->find('first', array( + 'recursive' => -1, + 'conditions' => array( + 'UserSetting.user_id' => $userSetting['user_id'], + 'UserSetting.setting' => $userSetting['setting'] + ) + )); + if (empty($existingSetting)) { + $this->create(); + } else { + $userSetting['id'] = $existingSetting['UserSetting']['id']; + } + // save the setting + $result = $this->save(array('UserSetting' => $userSetting)); + return true; + } } diff --git a/app/View/Attributes/add.ctp.bk b/app/View/Attributes/add.ctp.bk new file mode 100644 index 000000000..89bf37355 --- /dev/null +++ b/app/View/Attributes/add.ctp.bk @@ -0,0 +1,189 @@ +
+Form->create('Attribute', array('id', 'url' => '/attributes/' . $url_params)); +?> +
+ +
+ +
+ Form->hidden('event_id'); + echo $this->Form->input('category', array( + 'empty' => __('(choose one)'), + 'label' => __('Category ') . $this->element('formInfo', array('type' => 'category')), + )); + echo $this->Form->input('type', array( + 'empty' => __('(first choose category)'), + 'label' => __('Type ') . $this->element('formInfo', array('type' => 'type')), + )); + + $initialDistribution = 5; + if (Configure::read('MISP.default_attribute_distribution') != null) { + if (Configure::read('MISP.default_attribute_distribution') === 'event') { + $initialDistribution = 5; + } else { + $initialDistribution = Configure::read('MISP.default_attribute_distribution'); + } + } + + ?> +
+ array($distributionLevels), + 'label' => __('Distribution ') . $this->element('formInfo', array('type' => 'distribution')), + ); + + if ($action == 'add') { + $distArray['selected'] = $initialDistribution; + } + + echo $this->Form->input('distribution', $distArray); + ?> + + Form->input('value', array( + 'type' => 'textarea', + 'error' => array('escape' => false), + 'div' => 'input clear', + 'class' => 'input-xxlarge' + )); + ?> +
+ Form->input('comment', array( + 'type' => 'text', + 'label' => __('Contextual Comment'), + 'error' => array('escape' => false), + 'div' => 'input clear', + 'class' => 'input-xxlarge' + )); + ?> +
+ Form->input('to_ids', array( + 'label' => __('for Intrusion Detection System'), + 'type' => 'checkbox' + )); + echo $this->Form->input('batch_import', array( + 'type' => 'checkbox' + )); + echo '
'; + echo $this->Form->input('disable_correlation', array( + 'type' => 'checkbox' + )); + ?> +
+
+ + +
+ + +
+ + Form->button('Submit', array('class' => 'btn btn-primary')); + endif; + echo $this->Form->end(); + ?> +
+element('/genericElements/SideMenu/side_menu', array('menuList' => 'event', 'menuItem' => 'addAttribute', 'event' => $event)); + } +?> + +Js->writeBuffer(); // Write cached scripts diff --git a/app/View/Dashboards/Widgets/BarChart.ctp b/app/View/Dashboards/Widgets/BarChart.ctp new file mode 100644 index 000000000..93d1472c8 --- /dev/null +++ b/app/View/Dashboards/Widgets/BarChart.ctp @@ -0,0 +1,32 @@ + + $count) { + $value = $count; + if (!empty($data['logarithmic'])) { + $value = $data['logarithmic'][$entry]; + } + echo sprintf( + '', + 'text-align:right;width:33%;', + h($entry), + 'width:100%', + sprintf( + '
%s
', + h($entry) . ': ' . h($count), + sprintf( + 'background-color:%s; width:%s; color:white; text-align:center;', + (empty($data['colours'][$entry]) ? '#0088cc' : h($data['colours'][$entry])), + 100 * h($value) / $max . '%;' + ), + h($count) + ), + ' ' + ); + } +?> +
%s%s
diff --git a/app/View/Dashboards/Widgets/SimpleList.ctp b/app/View/Dashboards/Widgets/SimpleList.ctp new file mode 100644 index 000000000..56821a0ce --- /dev/null +++ b/app/View/Dashboards/Widgets/SimpleList.ctp @@ -0,0 +1,9 @@ +%s: %s%s', + h($element['title']), + empty($element['value']) ? '' : h($element['value']), + empty($element['html']) ? '' : $element['html'] + ); + } diff --git a/app/View/Dashboards/add.ctp b/app/View/Dashboards/add.ctp new file mode 100644 index 000000000..923303578 --- /dev/null +++ b/app/View/Dashboards/add.ctp @@ -0,0 +1,90 @@ + $desc) { + $paramsHtml .= sprintf( + '%s: %s
', + h($param), + h($desc) + ); + } + } + echo $this->element('genericElements/Form/genericForm', array( + 'form' => $this->Form, + 'url' => 'updateSettings', + 'data' => array( + 'title' => __('Add Widget'), + 'model' => 'Dashboard', + 'fields' => array( + array( + 'field' => 'widget', + 'class' => 'input span6', + 'options' => Hash::combine($data['widget_options'], '{s}.widget', '{s}.title') + ), + array( + 'field' => 'width', + 'class' => 'input', + 'type' => 'number', + 'default' => 1, + 'stayInLine' => 1 + ), + array( + 'field' => 'height', + 'type' => 'number', + 'class' => 'input', + 'default' => 1 + ), + array( + 'field'=> 'config', + 'type' => 'textarea', + 'class' => 'input span6', + 'div' => 'input clear', + 'label' => __('Config') + ) + ), + 'submit' => array( + 'action' => 'edit', + 'ajaxSubmit' => sprintf( + "submitDashboardAddWidget()" + ) + ), + 'description' => '

Parameters

' + ) + )); +?> + +Js->writeBuffer(); // Write cached scripts diff --git a/app/View/Dashboards/edit.ctp b/app/View/Dashboards/edit.ctp new file mode 100644 index 000000000..834278f12 --- /dev/null +++ b/app/View/Dashboards/edit.ctp @@ -0,0 +1,43 @@ + $desc) { + $paramsHtml .= sprintf( + '%s: %s
', + h($param), + h($desc) + ); + } + } + echo $this->element('genericElements/Form/genericForm', array( + 'form' => $this->Form, + 'url' => 'updateSettings', + 'data' => array( + 'title' => __('Edit Widget'), + 'model' => 'Dashboard', + 'fields' => array( + array( + 'field'=> 'config', + 'type' => 'textarea', + 'class' => 'input span6', + 'div' => 'input clear', + 'label' => __('Config'), + 'default' => empty($data['config']) ? '' : json_encode($data['config'], JSON_PRETTY_PRINT) + ) + ), + 'submit' => array( + 'action' => 'edit', + 'ajaxSubmit' => sprintf( + "submitDashboardForm('%s')", + h($data['id']) + ) + ), + 'description' => sprintf( + '

%s

Parameters

%s

', + h($data['description']), + $paramsHtml + ) + ) + )); +?> diff --git a/app/View/Dashboards/get_empty_widget.ctp b/app/View/Dashboards/get_empty_widget.ctp new file mode 100644 index 000000000..dfb603389 --- /dev/null +++ b/app/View/Dashboards/get_empty_widget.ctp @@ -0,0 +1 @@ +element('/dashboard/widget', array('widget' => $widget, 'k' => $k)); ?> diff --git a/app/View/Dashboards/index.ctp b/app/View/Dashboards/index.ctp new file mode 100644 index 000000000..a614e1f35 --- /dev/null +++ b/app/View/Dashboards/index.ctp @@ -0,0 +1,50 @@ +
+
+ $widget) { + $layout .= $this->element('/dashboard/widget', array('widget' => $widget, 'k' => $k)); + } + echo $layout; + ?> +
+ +
+element('/genericElements/SideMenu/side_menu', array('menuList' => 'dashboard', 'menuItem' => 'dashboardIndex')); +?> + diff --git a/app/View/Elements/dashboard/widget.ctp b/app/View/Elements/dashboard/widget.ctp new file mode 100644 index 000000000..17d360810 --- /dev/null +++ b/app/View/Elements/dashboard/widget.ctp @@ -0,0 +1,26 @@ +%s
 
', + h($k), + isset($widget['position']['x']) ? h($widget['position']['x']) : 1, + isset($widget['position']['y']) ? h($widget['position']['y']) : 1, + isset($widget['position']['width']) ? h($widget['position']['width']) : 2, + isset($widget['position']['height']) ? h($widget['position']['height']) : 2, + 'border: 1px solid #0088cc;', + empty($widget['config']) ? '[]' : h(json_encode($widget['config'])), + h($widget['widget']), + sprintf( + '
%s %s %s
%s
', + empty($widget['config']['alias']) ? h($widget['title']) : h($widget['config']['alias']), + sprintf( + '', + __('Configure widget') + ), + sprintf( + '', + __('Remove widget') + ), + ' ' + ) + ); +?> diff --git a/app/View/Elements/genericElements/Form/genericForm.ctp b/app/View/Elements/genericElements/Form/genericForm.ctp index bab2eb85e..6df454402 100644 --- a/app/View/Elements/genericElements/Form/genericForm.ctp +++ b/app/View/Elements/genericElements/Form/genericForm.ctp @@ -96,7 +96,8 @@ sprintf( '', sprintf( - '%s
%s%s
%s%s', + '%s%s
%s%s
%s%s', + empty($data['description']) ? '' : $data['description'], $formCreate, $ajaxFlashMessage, $fieldsString, diff --git a/app/View/Elements/genericElements/SideMenu/side_menu.ctp b/app/View/Elements/genericElements/SideMenu/side_menu.ctp index a6fa8ba0d..b483bc1c7 100644 --- a/app/View/Elements/genericElements/SideMenu/side_menu.ctp +++ b/app/View/Elements/genericElements/SideMenu/side_menu.ctp @@ -2,6 +2,22 @@