mirror of https://github.com/MISP/MISP
new: First revision of the new sightings system
commit
94c01d5896
|
@ -1 +1 @@
|
|||
{"major":2, "minor":4, "hotfix":63}
|
||||
{"major":2, "minor":4, "hotfix":65}
|
||||
|
|
|
@ -47,7 +47,7 @@ class AppController extends Controller {
|
|||
public $helpers = array('Utility');
|
||||
|
||||
private $__jsVersion = '2.4.62';
|
||||
public $pyMispVersion = '2.4.63';
|
||||
public $pyMispVersion = '2.4.65';
|
||||
public $phpmin = '5.5.9';
|
||||
public $phprec = '7.0.0';
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ class OrganisationsController extends AppController {
|
|||
if ($this->_isRest()) {
|
||||
if (!isset($this->request->data['Organisation']['local'])) {
|
||||
$this->request->data['Organisation']['local'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->Organisation->save($this->request->data)) {
|
||||
if ($this->_isRest()) {
|
||||
|
@ -212,6 +212,13 @@ class OrganisationsController extends AppController {
|
|||
}
|
||||
|
||||
public function view($id) {
|
||||
if (Validation::uuid($id)) {
|
||||
$temp = $this->Organisation->find('first', array('recursive' => -1, 'fields' => array('Organisation.id'), 'conditions' => array('Organisation.uuid' => $id)));
|
||||
if (empty($temp)) throw new NotFoundException('Invalid organisation.');
|
||||
$id = $temp['Organisation']['id'];
|
||||
} else if (!is_numeric($id)) {
|
||||
throw new NotFoundException('Invalid organisation.');
|
||||
}
|
||||
$this->Organisation->id = $id;
|
||||
if (!$this->Organisation->exists()) throw new NotFoundException('Invalid organisation');
|
||||
$fullAccess = false;
|
||||
|
@ -230,7 +237,7 @@ class OrganisationsController extends AppController {
|
|||
if ($fullAccess) {
|
||||
$creator = $this->Organisation->User->find('first', array(
|
||||
'conditions' => array('User.id' => $org['Organisation']['created_by']),
|
||||
'fields' => array('email'),
|
||||
'fields' => array('email'),
|
||||
'recursive' => -1
|
||||
)
|
||||
);
|
||||
|
|
|
@ -18,46 +18,108 @@ class SightingsController extends AppController {
|
|||
// takes an attribute ID or UUID
|
||||
public function add($id = false) {
|
||||
if (!$this->userRole['perm_add']) throw new MethodNotAllowedException('You are not authorised to add sightings data as you don\'t have write access.');
|
||||
if (!$this->request->is('post')) throw new MethodNotAllowedException('This action can only be accessed via a post request.');
|
||||
$now = time();
|
||||
$values = false;
|
||||
$timestamp = false;
|
||||
$error = false;
|
||||
if ($id === 'stix') {
|
||||
$result = $this->Sighting->handleStixSighting(file_get_contents('php://input'));
|
||||
if ($result['success']) {
|
||||
$result['data'] = json_decode($result['data'], true);
|
||||
$timestamp = isset($result['data']['timestamp']) ? strtotime($result['data']['timestamp']) : $now;
|
||||
$type = '0';
|
||||
$source = '';
|
||||
if (isset($result['data']['values'])) $values = $result['data']['values'];
|
||||
else $error = 'No valid values found could be extracted from the sightings document.';
|
||||
} $error = $result['message'];
|
||||
} else {
|
||||
if (isset($this->request->data['request'])) $this->request->data = $this->request->data['request'];
|
||||
if (isset($this->request->data['Sighting'])) $this->request->data = $this->request->data['Sighting'];
|
||||
$timestamp = isset($this->request->data['timestamp']) ? $this->request->data['timestamp'] : $now;
|
||||
if (isset($this->request->data['value'])) $this->request->data['values'] = array($this->request->data['value']);
|
||||
$values = isset($this->request->data['values']) ? $this->request->data['values'] : false;
|
||||
if (!$id && isset($this->request->data['id'])) $id = $this->request->data['id'];
|
||||
$type = isset($this->request->data['type']) ? $this->request->data['type'] : '0';
|
||||
$source = isset($this->request->data['type']) ? $this->request->data['type'] : '';
|
||||
}
|
||||
if (!$error) $result = $this->Sighting->saveSightings($id, $values, $timestamp, $this->Auth->user(), $type, $source);
|
||||
if ($result == 0) $error = 'No valid attributes found that would match the sighting criteria.';
|
||||
|
||||
if ($this->request->is('ajax')) {
|
||||
if ($error) {
|
||||
$error_message = 'Could not add the Sighting. Reason: ' . $error;
|
||||
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => $error_message)), 'status' => 200));
|
||||
if ($this->request->is('post')) {
|
||||
$now = time();
|
||||
$values = false;
|
||||
$timestamp = false;
|
||||
$error = false;
|
||||
if ($id === 'stix') {
|
||||
$result = $this->Sighting->handleStixSighting(file_get_contents('php://input'));
|
||||
if ($result['success']) {
|
||||
$result['data'] = json_decode($result['data'], true);
|
||||
$timestamp = isset($result['data']['timestamp']) ? strtotime($result['data']['timestamp']) : $now;
|
||||
$type = '0';
|
||||
$source = '';
|
||||
if (isset($result['data']['values'])) $values = $result['data']['values'];
|
||||
else $error = 'No valid values found could be extracted from the sightings document.';
|
||||
} $error = $result['message'];
|
||||
} else {
|
||||
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => $result . ' ' . $this->Sighting->type[$type] . (($result == 1) ? '' : 's') . ' added.')), 'status' => 200));
|
||||
if (isset($this->request->data['request'])) $this->request->data = $this->request->data['request'];
|
||||
if (isset($this->request->data['Sighting'])) $this->request->data = $this->request->data['Sighting'];
|
||||
if (!empty($this->request->data['date']) && !empty($this->request->data['time'])) {
|
||||
$timestamp = DateTime::createFromFormat('Y-m-d:H:i:s', $this->request->data['date'] . ':' . $this->request->data['time']);
|
||||
$timestamp = $timestamp->getTimestamp();
|
||||
} else {
|
||||
$timestamp = isset($this->request->data['timestamp']) ? $this->request->data['timestamp'] : $now;
|
||||
}
|
||||
if (isset($this->request->data['value'])) $this->request->data['values'] = array($this->request->data['value']);
|
||||
$values = isset($this->request->data['values']) ? $this->request->data['values'] : false;
|
||||
if (!$id && isset($this->request->data['id'])) $id = $this->request->data['id'];
|
||||
$type = isset($this->request->data['type']) ? $this->request->data['type'] : '0';
|
||||
$source = isset($this->request->data['source']) ? trim($this->request->data['source']) : '';
|
||||
}
|
||||
if (!$error) $result = $this->Sighting->saveSightings($id, $values, $timestamp, $this->Auth->user(), $type, $source);
|
||||
if ($result == 0) $error = 'No valid attributes found that would match the sighting criteria.';
|
||||
|
||||
if ($this->request->is('ajax')) {
|
||||
if ($error) {
|
||||
$error_message = 'Could not add the Sighting. Reason: ' . $error;
|
||||
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => $error_message)), 'status' => 200));
|
||||
} else {
|
||||
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => $result . ' ' . $this->Sighting->type[$type] . (($result == 1) ? '' : 's') . ' added.')), 'status' => 200));
|
||||
}
|
||||
} else {
|
||||
if ($error) {
|
||||
return $this->RestResponse->saveFailResponse('Sighting', 'add', $id, $error);
|
||||
} else {
|
||||
return $this->RestResponse->saveSuccessResponse('Sighting', 'add', $id, false, $result . ' ' . $this->Sighting->type[$type] . (($result == 1) ? '' : 's') . ' successfuly added.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if ($error) {
|
||||
return $this->RestResponse->saveFailResponse('Sighting', 'add', $id, $error);
|
||||
if (!$this->request->is('ajax')) {
|
||||
throw new MethodNotAllowedException('This method is only accessible via POST requests and ajax GET requests.');
|
||||
} else {
|
||||
return $this->RestResponse->saveSuccessResponse('Sighting', 'add', $id, false, $result . ' ' . $this->Sighting->type[$type] . (($result == 1) ? '' : 's') . ' successfuly added.');
|
||||
$this->layout = false;
|
||||
$this->loadModel('Attribute');
|
||||
if (empty($this->Attribute->fetchAttributes($this->Auth->user(), array('conditions' => array('Attribute.id' => $id))))) {
|
||||
throw new MethodNotAllowedExeption('Invalid Attribute.');
|
||||
}
|
||||
$this->set('id', $id);
|
||||
$this->render('ajax/add_sighting');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function advanced($id) {
|
||||
if (empty($id)) {
|
||||
throw new MethodNotAllowedException('Invalid attribute.');
|
||||
}
|
||||
$input_id = $id;
|
||||
$id = $this->Sighting->explodeIdList($id);
|
||||
$this->loadModel('Attribute');
|
||||
$attributes = $this->Attribute->fetchAttributes($this->Auth->user(), array('conditions' => array('Attribute.id' => $id)));
|
||||
if (empty($attributes)) {
|
||||
throw new MethodNotAllowedException('Invalid attribute.');
|
||||
}
|
||||
$this->set('id', $input_id);
|
||||
$this->render('/Sightings/ajax/advanced');
|
||||
}
|
||||
|
||||
public function quickDelete($id, $rawId, $context) {
|
||||
if (!$this->userRole['perm_modify_org']) throw new MethodNotAllowedException('You are not authorised to remove sightings data as you don\'t have permission to modify your organisation\'s data.');
|
||||
if (!$this->request->is('post')) {
|
||||
$this->set('id', $id);
|
||||
$sighting = $this->Sighting->find('first', array('conditions' => array('Sighting.id' => $id), 'recursive' => -1, 'fields' => array('Sighting.attribute_id')));
|
||||
$this->set('rawId', $rawId);
|
||||
$this->set('context', $context);
|
||||
$this->render('ajax/quickDeleteConfirmationForm');
|
||||
} else {
|
||||
if (!isset($id)) {
|
||||
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'errors' => 'Invalid request.')), 'status' => 200));
|
||||
} else {
|
||||
$sighting = $this->Sighting->find('first', array('conditions' => array('Sighting.id' => $id), 'recursive' => -1));
|
||||
if (empty($sighting)) {
|
||||
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'errors' => 'Invalid sighting.')), 'status' => 200));
|
||||
}
|
||||
if (!$this->_isSiteAdmin() && $sighting['Sighting']['org_id'] != $this->Auth->user('org_id')) {
|
||||
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'errors' => 'Invalid sighting.')), 'status' => 200));
|
||||
}
|
||||
$result = $this->Sighting->delete($id);
|
||||
if ($result) {
|
||||
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'Sighting deleted.')), 'status' => 200));
|
||||
} else {
|
||||
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'errors' => 'Sighting could not be deleted')), 'status' => 200));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -107,8 +169,10 @@ class SightingsController extends AppController {
|
|||
return $this->RestResponse->viewData($sightings);
|
||||
}
|
||||
|
||||
public function viewSightings($id, $context = 'attribute') {
|
||||
public function listSightings($id, $context = 'attribute', $org_id = false) {
|
||||
$this->loadModel('Event');
|
||||
$rawId = $id;
|
||||
$id = $this->Sighting->explodeIdList($id);
|
||||
if ($context === 'attribute') {
|
||||
$object = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array('conditions' => array('Attribute.id' => $id, 'Attribute.deleted' => 0)));
|
||||
} else {
|
||||
|
@ -120,25 +184,84 @@ class SightingsController extends AppController {
|
|||
if (empty($object)) {
|
||||
throw new MethodNotAllowedException('Invalid object.');
|
||||
}
|
||||
$results = array();
|
||||
$csv = array();
|
||||
foreach (array('0', '1') as $type) {
|
||||
$raw[$type] = $this->Sighting->find('all', array(
|
||||
'conditions' => array('Sighting.' . $context . '_id' => $id, 'Sighting.type' => $type),
|
||||
'recursive' => -1,
|
||||
'contain' => array('Organisation.name')
|
||||
));
|
||||
foreach ($raw[$type] as $sighting) {
|
||||
$results[$type][date('Y-m-d', $sighting['Sighting']['date_sighting'])][] = $sighting;
|
||||
}
|
||||
$conditions = array(
|
||||
'Sighting.' . $context . '_id' => $id
|
||||
);
|
||||
if ($org_id) {
|
||||
$conditions[] = array('Sighting.org_id' => $org_id);
|
||||
}
|
||||
$csv = array('0' => '', '1' => '');
|
||||
$sightings = $this->Sighting->find('all', array(
|
||||
'conditions' => $conditions,
|
||||
'recursive' => -1,
|
||||
'contain' => array('Organisation.name'),
|
||||
'order' => array('Sighting.date_sighting DESC')
|
||||
));
|
||||
$this->set('org_id', $org_id);
|
||||
$this->set('rawId', $rawId);
|
||||
$this->set('context', $context);
|
||||
$this->set('types', array('Sighting', 'False-positive', 'Expiration'));
|
||||
$this->set('sightings', $sightings);
|
||||
$this->layout = false;
|
||||
$this->render('ajax/list_sightings');
|
||||
}
|
||||
|
||||
public function viewSightings($id, $context = 'attribute') {
|
||||
$this->loadModel('Event');
|
||||
$id = $this->Sighting->explodeIdList($id);
|
||||
if ($context === 'attribute') {
|
||||
$attribute_id = $id;
|
||||
$object = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array('conditions' => array('Attribute.id' => $id, 'Attribute.deleted' => 0)));
|
||||
if (empty($object)) {
|
||||
throw new MethodNotAllowedException('Invalid object.');
|
||||
}
|
||||
$eventIds = array();
|
||||
foreach ($object as $k => $v) {
|
||||
$eventIds[] = $v['Attribute']['event_id'];
|
||||
}
|
||||
$events = $this->Event->fetchEvent($this->Auth->user(), array('eventid' => $eventIds));
|
||||
} else {
|
||||
$attribute_id = false;
|
||||
// let's set the context to event here, since we reuse the variable later on for some additional lookups.
|
||||
// Passing $context = 'org' could have interesting results otherwise...
|
||||
$context = 'event';
|
||||
$events = $this->Event->fetchEvent($this->Auth->user(), array('eventid' => $id));
|
||||
|
||||
}
|
||||
if (empty($events)) {
|
||||
throw new MethodNotAllowedException('Invalid object.');
|
||||
}
|
||||
$results = array();
|
||||
$raw = array();
|
||||
foreach ($events as $event) {
|
||||
$raw = array_merge($raw, $this->Sighting->attachToEvent($event, $this->Auth->user(), $attribute_id));
|
||||
}
|
||||
foreach ($raw as $sighting) {
|
||||
$results[$sighting['type']][date('Ymd', $sighting['date_sighting'])][] = $sighting;
|
||||
}
|
||||
$tsv = 'date\tSighting\tFalse-positive\n';
|
||||
$dataPoints = array();
|
||||
$startDate = (date('Ymd') - 3);
|
||||
$details = array();
|
||||
foreach ($results as $type => $data) {
|
||||
foreach ($data as $date => $sighting) {
|
||||
$csv[$type] .= $date . ', ' . count($sighting) . PHP_EOL;
|
||||
if ($date < $startDate) {
|
||||
$startDate = $date;
|
||||
}
|
||||
$temp = array();
|
||||
foreach ($sighting as $sightingInstance) {
|
||||
$temp[$sightingInstance['Organisation']['name']] = isset($temp[$sightingInstance['Organisation']['name']]) ? $temp[$sightingInstance['Organisation']['name']] + 1 : 1;
|
||||
}
|
||||
$dataPoints[$date][$type] = array('count' => count($sighting), 'details' => $temp);
|
||||
}
|
||||
}
|
||||
$this->set('csv', $csv);
|
||||
for ($i = $startDate; $i < date('Ymd') + 1; $i++) {
|
||||
if (checkdate(substr($i, 4, 2), substr($i, 6, 2), substr($i, 0, 4))) {
|
||||
$tsv .= $i . '\t' . (isset($dataPoints[$i][0]['count']) ? $dataPoints[$i][0]['count'] : 0) . '\t' . (isset($dataPoints[$i][1]['count']) ? $dataPoints[$i][1]['count'] : 0) . '\n';
|
||||
$details[$i][0] = isset($dataPoints[$i][0]['details']) ? $dataPoints[$i][0]['details'] : array();
|
||||
$details[$i][1] = isset($dataPoints[$i][1]['details']) ? $dataPoints[$i][1]['details'] : array();
|
||||
}
|
||||
}
|
||||
$this->set('tsv', $tsv);
|
||||
$this->set('results', $results);
|
||||
$this->layout = 'ajax';
|
||||
$this->render('ajax/view_sightings');
|
||||
|
|
|
@ -41,7 +41,7 @@ class AppModel extends Model {
|
|||
42 => false, 44 => false, 45 => false, 49 => true, 50 => false,
|
||||
51 => false, 52 => false, 55 => true, 56 => true, 57 => true,
|
||||
58 => false, 59 => false, 60 => false, 61 => false, 62 => false,
|
||||
63 => false, 64 => false
|
||||
63 => false, 64 => false, 65 => false
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -118,7 +118,6 @@ class AppModel extends Model {
|
|||
public function updateDatabase($command) {
|
||||
$dataSourceConfig = ConnectionManager::getDataSource('default')->config;
|
||||
$dataSource = $dataSourceConfig['datasource'];
|
||||
$sql = '';
|
||||
$sqlArray = array();
|
||||
$indexArray = array();
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
|
@ -582,6 +581,33 @@ class AppModel extends Model {
|
|||
$sqlArray[] = 'ALTER TABLE event_blacklists CHANGE comment comment TEXT CHARACTER SET utf8 COLLATE utf8_unicode_ci;';
|
||||
break;
|
||||
case '2.4.64':
|
||||
$indexArray[] = array('feeds', 'input_source');
|
||||
$indexArray[] = array('attributes', 'value1', 255);
|
||||
$indexArray[] = array('attributes', 'value2', 255);
|
||||
$indexArray[] = array('attributes', 'type');
|
||||
$indexArray[] = array('galaxy_reference', 'galaxy_cluster_id');
|
||||
$indexArray[] = array('galaxy_reference', 'referenced_galaxy_cluster_id');
|
||||
$indexArray[] = array('galaxy_reference', 'referenced_galaxy_cluster_value', 255);
|
||||
$indexArray[] = array('galaxy_reference', 'referenced_galaxy_cluster_type', 255);
|
||||
$indexArray[] = array('correlations', '1_event_id');
|
||||
$indexArray[] = array('warninglist_entries', 'warninglist_id');
|
||||
$indexArray[] = array('galaxy_clusters', 'value', 255);
|
||||
$indexArray[] = array('galaxy_clusters', 'tag_name');
|
||||
$indexArray[] = array('galaxy_clusters', 'uuid');
|
||||
$indexArray[] = array('galaxy_clusters', 'type');
|
||||
$indexArray[] = array('galaxies', 'name');
|
||||
$indexArray[] = array('galaxies', 'uuid');
|
||||
$indexArray[] = array('galaxies', 'type');
|
||||
break;
|
||||
case '2.4.65':
|
||||
$sqlArray[] = 'ALTER TABLE feeds CHANGE `enabled` `enabled` tinyint(1) DEFAULT 0;';
|
||||
$sqlArray[] = 'ALTER TABLE feeds CHANGE `default` `default` tinyint(1) DEFAULT 0;';
|
||||
$sqlArray[] = 'ALTER TABLE feeds CHANGE `distribution` `distribution` tinyint(4) NOT NULL DEFAULT 0;';
|
||||
$sqlArray[] = 'ALTER TABLE feeds CHANGE `sharing_group_id` `sharing_group_id` int(11) NOT NULL DEFAULT 0;';
|
||||
$sqlArray[] = 'ALTER TABLE attributes CHANGE `comment` `comment` text COLLATE utf8_bin;';
|
||||
break;
|
||||
case '2.4.66':
|
||||
$sqlArray[] = 'ALTER TABLE shadow_attributes CHANGE old_id old_id int(11) DEFAULT 0;';
|
||||
$sqlArray[] = 'ALTER TABLE sightings ADD COLUMN uuid varchar(255) COLLATE utf8_bin DEFAULT "";';
|
||||
$sqlArray[] = 'ALTER TABLE sightings ADD COLUMN source varchar(255) COLLATE utf8_bin DEFAULT "";';
|
||||
$sqlArray[] = 'ALTER TABLE sightings ADD COLUMN type int(11) DEFAULT 0;';
|
||||
|
@ -606,7 +632,6 @@ class AppModel extends Model {
|
|||
return false;
|
||||
break;
|
||||
}
|
||||
if (!isset($sqlArray)) $sqlArray = array($sql);
|
||||
foreach ($sqlArray as $sql) {
|
||||
try {
|
||||
$this->query($sql);
|
||||
|
@ -634,33 +659,12 @@ class AppModel extends Model {
|
|||
'change' => 'The executed SQL query was: ' . $sql . PHP_EOL . ' The returned error is: ' . $e->getMessage()
|
||||
));
|
||||
}
|
||||
foreach ($indexArray as $iA) {
|
||||
try {
|
||||
$this->__addIndex($iA[0], $iA[1]);
|
||||
$this->Log->create();
|
||||
$this->Log->save(array(
|
||||
'org' => 'SYSTEM',
|
||||
'model' => 'Server',
|
||||
'model_id' => 0,
|
||||
'email' => 'SYSTEM',
|
||||
'action' => 'update_database',
|
||||
'user_id' => 0,
|
||||
'title' => 'Successfuly executed the SQL query for ' . $command,
|
||||
'change' => 'New index for field ' . $iA[1] . ' added to table ' . $iA[0],
|
||||
));
|
||||
} catch (Exception $e) {
|
||||
$this->Log->create();
|
||||
$this->Log->save(array(
|
||||
'org' => 'SYSTEM',
|
||||
'model' => 'Server',
|
||||
'model_id' => 0,
|
||||
'email' => 'SYSTEM',
|
||||
'action' => 'update_database',
|
||||
'user_id' => 0,
|
||||
'title' => 'Issues executing the SQL query for ' . $command,
|
||||
'change' => 'Failed to add index for field ' . $iA[1] . ' for table ' . $iA[0],
|
||||
));
|
||||
}
|
||||
}
|
||||
foreach ($indexArray as $iA) {
|
||||
if (isset($iA[2])) {
|
||||
$this->__addIndex($iA[0], $iA[1], $iA[2]);
|
||||
} else {
|
||||
$this->__addIndex($iA[0], $iA[1]);
|
||||
}
|
||||
}
|
||||
if ($clean) $this->cleanCacheFiles();
|
||||
|
@ -712,16 +716,18 @@ class AppModel extends Model {
|
|||
if ($dataSource == 'Database/Postgres') {
|
||||
$addIndex = "CREATE INDEX idx_" . $table . "_" . $field . " ON " . $table . " (" . $field . ");";
|
||||
} else {
|
||||
if (isset($length)) {
|
||||
if (!$length) {
|
||||
$addIndex = "ALTER TABLE `" . $table . "` ADD INDEX `" . $field . "` (`" . $field . "`);";
|
||||
} else {
|
||||
$addIndex = "ALTER TABLE `" . $table . "` ADD INDEX `" . $field . "` (`" . $field . "`(" . $length . "));";
|
||||
}
|
||||
}
|
||||
$result = true;
|
||||
$duplicate = false;
|
||||
try {
|
||||
$this->query($addIndex);
|
||||
} catch (Exception $e) {
|
||||
$duplicate = (strpos($e->getMessage(), '1061') !== false);
|
||||
$result = false;
|
||||
}
|
||||
$this->Log->create();
|
||||
|
@ -732,8 +738,8 @@ class AppModel extends Model {
|
|||
'email' => 'SYSTEM',
|
||||
'action' => 'update_database',
|
||||
'user_id' => 0,
|
||||
'title' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table,
|
||||
'change' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table,
|
||||
'title' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table . ($duplicate ? ' (index already set)' : ''),
|
||||
'change' => ($result ? 'Added index ' : 'Failed to add index ') . $field . ' to ' . $table . ($duplicate ? ' (index already set)' : ''),
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -46,10 +46,13 @@ class Sighting extends AppModel {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function attachToEvent(&$event, $user) {
|
||||
public function attachToEvent($event, $user, $attribute_id = false) {
|
||||
$ownEvent = false;
|
||||
if ($user['Role']['perm_site_admin'] || $event['Event']['org_id'] == $user['org_id']) $ownEvent = true;
|
||||
$conditions = array('Sighting.event_id' => $event['Event']['id']);
|
||||
if ($attribute_id) {
|
||||
$conditions[] = array('Sighting.attribute_id' => $attribute_id);
|
||||
}
|
||||
if (!$ownEvent && (!Configure::read('Plugin.Sightings_policy') || Configure::read('Plugin.Sightings_policy') == 0)) {
|
||||
$conditions['Sighting.org_id'] = $user['org_id'];
|
||||
}
|
||||
|
@ -109,6 +112,10 @@ class Sighting extends AppModel {
|
|||
if (empty($attributes)) return 0;
|
||||
$sightingsAdded = 0;
|
||||
foreach ($attributes as $attribute) {
|
||||
if ($type === '2') {
|
||||
// remove existing expiration by the same org if it exists
|
||||
$this->deleteAll(array('Sighting.org_id' => $user['org_id'], 'Sighting.type' => $type, 'Sighting.attribute_id' => $attribute['Attribute']['id']));
|
||||
}
|
||||
$this->create();
|
||||
$sighting = array(
|
||||
'attribute_id' => $attribute['Attribute']['id'],
|
||||
|
@ -159,4 +166,17 @@ class Sighting extends AppModel {
|
|||
$this->saveMany($sightings);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function explodeIdList($id) {
|
||||
if (strpos($id, '|')) {
|
||||
$id = explode('|', $id);
|
||||
foreach ($id as $k => $v) {
|
||||
if (!is_numeric($v)) {
|
||||
unset($id[$k]);
|
||||
}
|
||||
}
|
||||
$id = array_values($id);
|
||||
}
|
||||
return $id;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
$attributeOwnSightings = array();
|
||||
$attributeSightingsPopover = array();
|
||||
$sightingsData = array();
|
||||
$sparklineData = array();
|
||||
$startDates = array();
|
||||
if (!empty($event['Sighting'])) {
|
||||
foreach ($event['Sighting'] as $sighting) {
|
||||
$type = $sightingTypes[$sighting['type']];
|
||||
|
@ -27,12 +29,43 @@
|
|||
if (!isset($sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName])) {
|
||||
$sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName] = array('count' => 1, 'date' => $sighting['date_sighting']);
|
||||
} else {
|
||||
if (!isset($startDates[$sighting['attribute_id']]) || $startDates[$sighting['attribute_id']] > $sighting['date_sighting']) {
|
||||
$startDates[$sighting['attribute_id']] = $sighting['date_sighting'];
|
||||
}
|
||||
$sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName]['count']++;
|
||||
if ($sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName]['date'] < $sighting['date_sighting']) {
|
||||
$sightingsData[$sighting['attribute_id']][$type]['orgs'][$orgName]['date'] = $sighting['date_sighting'];
|
||||
}
|
||||
}
|
||||
$date = date("Ymd", $sighting['date_sighting']);
|
||||
if (!isset($sparklineData[$sighting['attribute_id']][$type][$date])) {
|
||||
$sparklineData[$sighting['attribute_id']][$type][$date] = 1;
|
||||
} else {
|
||||
$sparklineData[$sighting['attribute_id']][$type][$date]++;
|
||||
}
|
||||
}
|
||||
$csv = array();
|
||||
$to = new DateTime();
|
||||
$from = new DateTime();
|
||||
foreach ($sparklineData as $aid => $data) {
|
||||
foreach ($data as $type => $sighting) {
|
||||
$from->setTimestamp(($startDates[$aid] - 259200));
|
||||
for ($date = clone $from; $date < $to; $date->modify('+1 day')) {
|
||||
if (!isset($csv[$aid][$type])) {
|
||||
$csv[$aid][$type] = 'Date,Close\n';
|
||||
}
|
||||
$currentDate = $date->format('Ymd');
|
||||
if (isset($sighting[$currentDate])) {
|
||||
$csv[$aid][$type] .= $currentDate . ',' . $sighting[$currentDate] . '\n';
|
||||
} else {
|
||||
$csv[$aid][$type] .= $currentDate . ',0\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
unset($sparklineData);
|
||||
foreach ($sightingsData as $aid => $data) {
|
||||
$sightingsData[$aid]['html'] = '';
|
||||
foreach ($data as $type => $typeData) {
|
||||
|
@ -122,6 +155,9 @@
|
|||
<span id="multi-delete-button" title="Delete selected Attributes" class = "icon-trash mass-select useCursorPointer" onClick="multiSelectAction(<?php echo $event['Event']['id']; ?>, 'deleteAttributes');"></span>
|
||||
<span id="multi-accept-button" title="Accept selected Proposals" class="icon-ok mass-proposal-select useCursorPointer" onClick="multiSelectAction(<?php echo $event['Event']['id']; ?>, 'acceptProposals');"></span>
|
||||
<span id="multi-discard-button" title="Discard selected Proposals" class = "icon-remove mass-proposal-select useCursorPointer" onClick="multiSelectAction(<?php echo $event['Event']['id']; ?>, 'discardProposals');"></span>
|
||||
<?php if (Configure::read('Plugin.Sightings_enable')): ?>
|
||||
<span id="multi-sighting-button" title="Sightings display for selected attributes" class = "icon-wrench mass-select useCursorPointer sightings_advanced_add" data-object-id="selected"></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="tabMenu tabMenuToolsBlock noPrint">
|
||||
<?php if ($mayModify): ?>
|
||||
|
@ -450,10 +486,21 @@
|
|||
echo $this->Form->end();
|
||||
?>
|
||||
</span>
|
||||
<?php
|
||||
$temp = array();
|
||||
if (isset($csv[$object['id']]['sighting'])) {
|
||||
$temp[0] = $csv[$object['id']]['sighting'];
|
||||
}
|
||||
if (isset($csv[$object['id']]['sighting'])) {
|
||||
$temp[1] = $csv[$object['id']]['false-positive'];
|
||||
}
|
||||
|
||||
echo $this->element('sparkline', array('id' => $object['id'], 'csv' => $temp));
|
||||
?>
|
||||
<span class="icon-thumbs-up useCursorPointer" onClick="addSighting('0', '<?php echo h($object['id']); ?>', '<?php echo h($event['Event']['id']);?>', '<?php echo h($page); ?>');"> </span>
|
||||
<span class="icon-thumbs-down useCursorPointer" onClick="addSighting('1', '<?php echo h($object['id']); ?>', '<?php echo h($event['Event']['id']);?>', '<?php echo h($page); ?>');"> </span>
|
||||
<span class="icon-wrench useCursorPointer" onClick="addSighting('<?php echo h($object['id']); ?>', '<?php echo h($event['Event']['id']);?>', '<?php echo h($page); ?>');"> </span>
|
||||
<span id="sightingCount_<?php echo h($object['id']); ?>" class="bold sightingsCounter_<?php echo h($object['id']); ?>" data-placement="top" data-toggle="popover" data-trigger="hover" data-content="<?php echo isset($sightingsData[$object['id']]['html']) ? $sightingsData[$object['id']]['html'] : ''; ?>">
|
||||
<span class="icon-wrench useCursorPointer sightings_advanced_add" data-object-id="<?php echo h($object['id']); ?>"> </span>
|
||||
<span id="sightingCount_<?php echo h($object['id']); ?>" class="bold sightingsCounter_<?php echo h($object['id']); ?>" data-placement="top" data-toggle="popover" data-trigger="hover" data-content="<?php echo isset($sightingsData[$object['id']]['html']) ? $sightingsData[$object['id']]['html'] : ''; ?>">
|
||||
<?php
|
||||
$s = (!empty($sightingsData[$object['id']]['sighting']['count']) ? $sightingsData[$object['id']]['sighting']['count'] : 0);
|
||||
$f = (!empty($sightingsData[$object['id']]['false-positive']['count']) ? $sightingsData[$object['id']]['false-positive']['count'] : 0);
|
||||
|
@ -616,6 +663,20 @@ attributes or the appropriate distribution level. If you think there is a mistak
|
|||
$('.screenshot').click(function() {
|
||||
screenshotPopup($(this).attr('src'), $(this).attr('title'));
|
||||
});
|
||||
$('.sightings_advanced_add').click(function() {
|
||||
var selected = [];
|
||||
var object_id = $(this).data('object-id');
|
||||
if (object_id == 'selected') {
|
||||
$(".select_attribute").each(function() {
|
||||
if ($(this).is(":checked")) {
|
||||
selected.push($(this).data("id"));
|
||||
}
|
||||
});
|
||||
object_id = selected.join('|');
|
||||
}
|
||||
url = "<?php echo $baseurl; ?>" + "/sightings/advanced/" + object_id;
|
||||
genericPopup(url, '#screenshot_box');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<!--
|
||||
Modified version of http://www.tnoda.com/blog/2013-12-19
|
||||
-->
|
||||
<?php
|
||||
echo $this->Html->script('d3');
|
||||
//echo $this->Html->css('sightingstyle');
|
||||
?>
|
||||
<div id="spark_<?php echo h($id); ?>"></div>
|
||||
<script>
|
||||
var width = 100;
|
||||
var height = 25;
|
||||
var x = d3.scale.linear().range([0, width - 2]);
|
||||
var y = d3.scale.linear().range([height - 4, 0]);
|
||||
var parseDate = d3.time.format("%Y%m%d").parse;
|
||||
var line = d3.svg.line()
|
||||
.interpolate("linear")
|
||||
.x(function(d) { return x(d.date); })
|
||||
.y(function(d) { return y(d.close); });
|
||||
function sparkline(elemId, data) {
|
||||
data.forEach(function(d) {
|
||||
d.date = parseDate(d.Date);
|
||||
d.close = +d.Close;
|
||||
});
|
||||
x.domain(d3.extent(data, function(d) { return d.date; }));
|
||||
y.domain(d3.extent(data, function(d) { return d.close; }));
|
||||
var svg = d3.select(elemId)
|
||||
.append('svg')
|
||||
.attr('width', width)
|
||||
.attr('height', height)
|
||||
.append('g')
|
||||
.attr('transform', 'translate(0, 2)');
|
||||
svg.append('path')
|
||||
.datum(data)
|
||||
.attr('class', 'sparkline')
|
||||
.attr('d', line);
|
||||
svg.append('circle')
|
||||
.attr('class', 'sparkcircle')
|
||||
.attr('cx', x(data[0].close))
|
||||
.attr('cy', y(data[0].date))
|
||||
.attr('r', 1.5);
|
||||
}
|
||||
|
||||
var myData = "<?php echo isset($csv[0]) ? $csv[0] : ''; ?>";
|
||||
if (myData != '') {
|
||||
var data = d3.csv.parse(myData);
|
||||
sparkline('#spark_<?php echo h($id); ?>', data);
|
||||
}
|
||||
</script>
|
|
@ -67,21 +67,21 @@ echo $this->Form->end();
|
|||
foreach ($formInfoTypes as $formInfoType => $humanisedName) {
|
||||
echo 'var ' . $formInfoType . 'FormInfoValues = {' . PHP_EOL;
|
||||
foreach ($info[$formInfoType] as $key => $formInfoData) {
|
||||
echo '"' . $key . '": "<span class=\"blue bold\">' . h($formInfoData['key']) . '</span>: ' . h($formInfoData['desc']) . '<br />",' . PHP_EOL;
|
||||
echo '"' . $key . '": "<span class=\"blue bold\">' . h($formInfoData['key']) . '</span>: ' . h($formInfoData['desc']) . '<br />",' . PHP_EOL;
|
||||
}
|
||||
echo '}' . PHP_EOL;
|
||||
}
|
||||
?>
|
||||
|
||||
|
||||
$('#EventDistribution').change(function() {
|
||||
if ($('#EventDistribution').val() == 4) $('#SGContainer').show();
|
||||
else $('#SGContainer').hide();
|
||||
});
|
||||
|
||||
|
||||
$("#EventDistribution, #EventAnalysis, #EventThreatLevelId").change(function() {
|
||||
initPopoverContent('Event');
|
||||
});
|
||||
|
||||
|
||||
$(document).ready(function() {
|
||||
if ($('#EventDistribution').val() == 4) $('#SGContainer').show();
|
||||
else $('#SGContainer').hide();
|
||||
|
|
|
@ -34,8 +34,8 @@
|
|||
</head>
|
||||
<body>
|
||||
<div id="popover_form" class="ajax_popover_form"></div>
|
||||
<div id="confirmation_box" class="confirmation_box"></div>
|
||||
<div id="screenshot_box" class="screenshot_box"></div>
|
||||
<div id="confirmation_box" class="confirmation_box"></div>
|
||||
<div id="gray_out" class="gray_out"></div>
|
||||
<div id="container">
|
||||
<?php echo $this->element('global_menu');
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<h3>Add Sighting</h3>
|
||||
<?php
|
||||
echo $this->Form->create('Sighting', array('id', 'url' => '/sightings/add/' . $id, 'style' => 'margin-bottom:0px;'));
|
||||
echo $this->Form->input('type', array(
|
||||
'options' => array('Sighting', 'Fase-positive', 'Expiration'),
|
||||
'default' => 2,
|
||||
'style' => 'width:230px;margin-right:0px;'
|
||||
));
|
||||
echo $this->Form->input('source', array(
|
||||
'placeholder' => 'honeypot, IDS sensor id, SIEM,...',
|
||||
'style' => 'width:447px;',
|
||||
'div' => array('style' => 'width:457px !important;')
|
||||
));
|
||||
echo $this->Form->label('Sighting date');
|
||||
echo $this->Form->input('date', array(
|
||||
'type' => 'text',
|
||||
'id' => 'datepicker',
|
||||
'default' => date('Y-m-d'),
|
||||
'style' => 'width:110px;',
|
||||
'div' => array('style' => 'width:120px !important;'),
|
||||
'label' => false
|
||||
));
|
||||
echo $this->Form->input('time', array(
|
||||
'class' => 'input-mini',
|
||||
'default' => date('H:i:s'),
|
||||
'id' => 'timepicker',
|
||||
'style' => 'width:120px;',
|
||||
'div' => array('style' => 'width:120px !important;'),
|
||||
'label' => false
|
||||
));
|
||||
?>
|
||||
<span id="submitButton" class="btn btn-primary" onClick="submitPopoverForm('<?php echo h($id);?>', 'addSighting')">Add</span>
|
||||
<div class="input clear"></div>
|
||||
<?php
|
||||
echo $this->Form->end();
|
||||
?>
|
|
@ -0,0 +1,59 @@
|
|||
<div class="sightings_advanced">
|
||||
<div class="popover-legend"><p><?php echo __('Sighting details'); ?></p></div>
|
||||
<div style="margin:10px;">
|
||||
<span id="sightingsGraphToggle" class="btn btn-primary qet toggle-left sightingsToggle" data-type="graph">Graph</span>
|
||||
<span id="sightingsListAllToggle" class="btn btn-inverse qet toggle sightingsToggle" data-type="all">All</span>
|
||||
<span id="sightingsListMyToggle" class="btn btn-inverse qet toggle sightingsToggle" data-type="org">My org</span>
|
||||
<span id="sightingsAddToggle" class="btn btn-inverse qet toggle-right sightingsToggle" data-type="add">Add sighting</span>
|
||||
</div>
|
||||
<div id="mainContents" style="margin-top:40px;padding:10px;">
|
||||
<div id="sightingsData" class="sightingTab"></div>
|
||||
<span style="float:right;margin-bottom:10px;" class="btn btn-inverse" id="cancel">Cancel</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
id = "<?php echo h($id); ?>";
|
||||
$('#cancel').click(function() {
|
||||
cancelPopoverForm();
|
||||
});
|
||||
$('#datepicker').datepicker({
|
||||
startDate: '-180d',
|
||||
endDate: '+1d',
|
||||
orientation: 'bottom',
|
||||
autoclose: true,
|
||||
format: 'yyyy-mm-dd'
|
||||
});
|
||||
$('#timepicker').timepicker({
|
||||
minuteStep: 1,
|
||||
showMeridian: false,
|
||||
showSeconds: true,
|
||||
maxHours: 24
|
||||
});
|
||||
loadSightingGraph(id, "attribute");
|
||||
});
|
||||
$('.sightingsToggle').click(function() {
|
||||
$('.sightingsToggle').removeClass('btn-primary');
|
||||
$('.sightingsToggle').addClass('btn-inverse');
|
||||
$(this).removeClass('btn-inverse');
|
||||
$(this).addClass('btn-primary');
|
||||
var type = $(this).data('type');
|
||||
$('.sightingTab').empty();
|
||||
if (type == 'graph') {
|
||||
loadSightingGraph(id, "attribute");
|
||||
} else if (type == 'add') {
|
||||
$.get( "/sightings/add/" + id, function(data) {
|
||||
$("#sightingsData").html(data);
|
||||
});
|
||||
} else {
|
||||
var org = "";
|
||||
if (type == 'org') org = "/<?php echo h($me['org_id']);?>"
|
||||
$.get( "/sightings/listSightings/" + id + "/attribute" + org, function(data) {
|
||||
$("#sightingsData").html(data);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?php echo $this->Js->writeBuffer(); // Write cached scripts
|
|
@ -0,0 +1,52 @@
|
|||
<div>
|
||||
<div id="org_id" class="hidden"><?php echo h($org_id); ?></div>
|
||||
<h2>Galaxies</h2>
|
||||
<table class="table table-striped table-hover table-condensed" style="display:block; overflow-y:auto;max-height:500px;">
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Organisation</th>
|
||||
<th>Type</th>
|
||||
<th>Source</th>
|
||||
<th>Event ID</th>
|
||||
<th>Attribute ID</th>
|
||||
<th class="actions">Actions</th>
|
||||
</tr>
|
||||
<?php
|
||||
foreach ($sightings as $item):
|
||||
?>
|
||||
<tr>
|
||||
<td class="short"><?php echo date('Y-m-d H:i:s', $item['Sighting']['date_sighting']);?></td>
|
||||
<td class="short">
|
||||
<?php
|
||||
$imgAbsolutePath = APP . WEBROOT_DIR . DS . 'img' . DS . 'orgs' . DS . h($item['Organisation']['name']) . '.png';
|
||||
if (file_exists($imgAbsolutePath)):
|
||||
echo $this->Html->image('orgs/' . h($item['Organisation']['name']) . '.png', array('alt' => h($item['Organisation']['name']), 'title' => h($item['Organisation']['name']), 'style' => 'width:24px; height:24px'));
|
||||
else:
|
||||
echo h($item['Organisation']['name']);
|
||||
endif;
|
||||
|
||||
?>
|
||||
</td>
|
||||
<td class="short">
|
||||
<?php
|
||||
echo $types[$item['Sighting']['type']];
|
||||
?>
|
||||
</td>
|
||||
<td class="short"><?php echo h($item['Sighting']['source']);?></td>
|
||||
<td class="short"><?php echo h($item['Sighting']['event_id']);?></td>
|
||||
<td class="short"><?php echo h($item['Sighting']['attribute_id']);?></td>
|
||||
<td class="short action-links">
|
||||
<?php
|
||||
if ($isSiteAdmin || ($item['Sighting']['org_id'] == $me['org_id'] && $perm_add)):
|
||||
?>
|
||||
<span class="icon-trash useCursorPointer" onClick="quickDeleteSighting('<?php echo h($item['Sighting']['id']); ?>', '<?php echo h($rawId); ?>', '<?php echo h($context); ?>');"></span>
|
||||
<?php
|
||||
endif;
|
||||
?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
endforeach;
|
||||
?>
|
||||
</table>
|
||||
</div>
|
|
@ -0,0 +1,24 @@
|
|||
<div class="confirmation">
|
||||
<?php
|
||||
echo $this->Form->create('Sighting', array('style' => 'margin:0px;', 'id' => 'PromptForm', 'url' => '/sightings/quickDelete/' . $id));
|
||||
?>
|
||||
<legend>Remove Sighting</legend>
|
||||
<div style="padding-left:5px;padding-right:5px;padding-bottom:5px;">
|
||||
<p>Remove sighting (<?php echo h($id); ?>)?</p>
|
||||
<table>
|
||||
<tr>
|
||||
<td style="vertical-align:top">
|
||||
<span id="PromptYesButton" class="btn btn-primary" onClick="removeSighting('<?php echo h($id); ?>', '<?php echo h($rawId); ?>', '<?php echo h($context); ?>');">Yes</span>
|
||||
</td>
|
||||
<td style="width:540px;">
|
||||
</td>
|
||||
<td style="vertical-align:top;">
|
||||
<span class="btn btn-inverse" id="PromptNoButton" onClick="cancelPrompt(1);">No</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<?php
|
||||
echo $this->Form->end();
|
||||
?>
|
||||
</div>
|
|
@ -0,0 +1,150 @@
|
|||
<?php
|
||||
echo $this->Html->script('d3');
|
||||
echo $this->Html->css('sightingstyle');
|
||||
?>
|
||||
<div id="graphContent" class="graphContent"></div>
|
||||
<script>
|
||||
var myData = "<?php echo $tsv; ?>";
|
||||
|
||||
var colours = {
|
||||
'Sighting': 'blue',
|
||||
'False-positive': 'red'
|
||||
}
|
||||
|
||||
var margin = {
|
||||
top: 20,
|
||||
right: 60,
|
||||
bottom: 30,
|
||||
left: 25
|
||||
},
|
||||
width = 980 - margin.left - margin.right,
|
||||
height = 300 - margin.top - margin.bottom;
|
||||
|
||||
var parseDate = d3.time.format("%Y%m%d").parse;
|
||||
|
||||
var x = d3.time.scale()
|
||||
.range([0, width]);
|
||||
|
||||
var y = d3.scale.linear()
|
||||
.range([height, 0]);
|
||||
|
||||
var color = d3.scale.category10();
|
||||
|
||||
var xAxis = d3.svg.axis()
|
||||
.scale(x)
|
||||
.orient("bottom");
|
||||
|
||||
var yAxis = d3.svg.axis()
|
||||
.scale(y)
|
||||
.orient("left");
|
||||
|
||||
var line = d3.svg.line()
|
||||
.interpolate("linear")
|
||||
.x(function(d) {
|
||||
return x(d.date);
|
||||
})
|
||||
.y(function(d) {
|
||||
return y(d.count);
|
||||
});
|
||||
|
||||
var svg = d3.select("#graphContent").append("svg")
|
||||
.attr("width", width + margin.left + margin.right)
|
||||
.attr("height", height + margin.top + margin.bottom)
|
||||
.append("g")
|
||||
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
|
||||
|
||||
var data = d3.tsv.parse(myData);
|
||||
|
||||
color.domain(d3.keys(data[0]).filter(function(key) {
|
||||
return key !== "date";
|
||||
}));
|
||||
|
||||
data.forEach(function(d) {
|
||||
d.date = parseDate(d.date);
|
||||
});
|
||||
|
||||
var sightings = color.domain().map(function(name) {
|
||||
return {
|
||||
name: name,
|
||||
values: data.map(function(d) {
|
||||
return {
|
||||
date: d.date,
|
||||
count: +d[name]
|
||||
};
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
x.domain(d3.extent(data, function(d) {
|
||||
return d.date;
|
||||
}));
|
||||
|
||||
y.domain([
|
||||
d3.min(sightings, function(c) {
|
||||
return d3.min(c.values, function(v) {
|
||||
return v.count;
|
||||
});
|
||||
}),
|
||||
d3.max(sightings, function(c) {
|
||||
return d3.max(c.values, function(v) {
|
||||
return v.count;
|
||||
});
|
||||
})
|
||||
]);
|
||||
|
||||
var legend = svg.selectAll('g')
|
||||
.data(sightings)
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', 'sightingsLegend');
|
||||
|
||||
legend.append('rect')
|
||||
.attr('x', width - 20)
|
||||
.attr('y', function(d, i) {
|
||||
return i * 20;
|
||||
})
|
||||
.attr('width', 10)
|
||||
.attr('height', 10)
|
||||
.style('fill', function(d) {
|
||||
return colours[d.name];
|
||||
});
|
||||
|
||||
legend.append('text')
|
||||
.attr('x', width - 8)
|
||||
.attr('y', function(d, i) {
|
||||
return (i * 20) + 9;
|
||||
})
|
||||
.text(function(d) {
|
||||
return d.name;
|
||||
});
|
||||
|
||||
svg.append("g")
|
||||
.attr("class", "x axis")
|
||||
.attr("transform", "translate(0," + height + ")")
|
||||
.call(xAxis);
|
||||
|
||||
svg.append("g")
|
||||
.attr("class", "y axis")
|
||||
.call(yAxis)
|
||||
.append("text")
|
||||
.attr("transform", "rotate(-90)")
|
||||
.attr("y", 6)
|
||||
.attr("dy", ".71em")
|
||||
.style("text-anchor", "end")
|
||||
.text("Count");
|
||||
|
||||
var sightings = svg.selectAll(".sightings")
|
||||
.data(sightings)
|
||||
.enter().append("g")
|
||||
.attr("class", "sightings");
|
||||
|
||||
sightings.append("path")
|
||||
.attr("class", "line")
|
||||
.attr("d", function(d) {
|
||||
return line(d.values);
|
||||
})
|
||||
.style("stroke", function(d) {
|
||||
return colours[d.name];
|
||||
});
|
||||
|
||||
</script>
|
|
@ -762,6 +762,20 @@ a.proposal_link_red:hover {
|
|||
color:white;
|
||||
}
|
||||
|
||||
.popover-legend {
|
||||
border-radius: 5px 5px 0px 0px;
|
||||
margin-bottom:5px;
|
||||
padding-left:0px;
|
||||
background-color:black;
|
||||
color:white;
|
||||
font-size: 21px;
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.popover-legend p {
|
||||
padding-left:10px;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
@ -1672,6 +1686,21 @@ table.table.table-striped tr.deleted_row td {
|
|||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.sparkline {
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
stroke-width: 0.5px;
|
||||
}
|
||||
.sparkcircle {
|
||||
fill: #f00;
|
||||
stroke: none;
|
||||
}
|
||||
|
||||
.sightings_advanced
|
||||
{
|
||||
width:1000px;
|
||||
}
|
||||
|
||||
@-webkit-keyframes rotation {
|
||||
from {-webkit-transform: rotate(0deg);}
|
||||
to {-webkit-transform: rotate(359deg);}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
.graphContent {
|
||||
font: 10px sans-serif;
|
||||
}
|
||||
|
||||
.axis path,
|
||||
.axis line {
|
||||
fill: none;
|
||||
stroke: #000;
|
||||
shape-rendering: crispEdges;
|
||||
}
|
||||
|
||||
.x.axis path {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.line {
|
||||
fill: none;
|
||||
stroke: steelblue;
|
||||
stroke-width: 1.5px;
|
||||
}
|
|
@ -14,6 +14,14 @@ function deleteObject(type, action, id, event) {
|
|||
});
|
||||
}
|
||||
|
||||
function quickDeleteSighting(id, rawId, context) {
|
||||
url = "/sightings/quickDelete/" + id + "/" + rawId + "/" + context;
|
||||
$.get(url, function(data) {
|
||||
$("#confirmation_box").fadeIn();
|
||||
$("#confirmation_box").html(data);
|
||||
});
|
||||
}
|
||||
|
||||
function publishPopup(id, type) {
|
||||
var action = "alert";
|
||||
if (type == "publish") action = "publish";
|
||||
|
@ -37,6 +45,8 @@ function genericPopup(url, popupTarget) {
|
|||
$.get(url, function(data) {
|
||||
$(popupTarget).html(data);
|
||||
$(popupTarget).fadeIn();
|
||||
left = ($(window).width() / 2) - ($(popupTarget).width() / 2);
|
||||
$(popupTarget).css({'left': left + 'px'});
|
||||
$("#gray_out").fadeIn();
|
||||
});
|
||||
}
|
||||
|
@ -64,9 +74,11 @@ function editTemplateElement(type, id) {
|
|||
});
|
||||
}
|
||||
|
||||
function cancelPrompt() {
|
||||
$("#confirmation_box").fadeIn();
|
||||
$("#gray_out").fadeOut();
|
||||
function cancelPrompt(isolated) {
|
||||
if (isolated == 'undefined') {
|
||||
$("#gray_out").fadeOut();
|
||||
}
|
||||
$("#confirmation_box").fadeOut();
|
||||
$("#confirmation_box").empty();
|
||||
}
|
||||
|
||||
|
@ -94,6 +106,33 @@ function submitDeletion(context_id, action, type, id) {
|
|||
});
|
||||
}
|
||||
|
||||
function removeSighting(id, rawid, context) {
|
||||
if (context != 'attribute') {
|
||||
context = 'event';
|
||||
}
|
||||
var formData = $('#PromptForm').serialize();
|
||||
$.ajax({
|
||||
beforeSend: function (XMLHttpRequest) {
|
||||
$(".loading").show();
|
||||
},
|
||||
data: formData,
|
||||
success:function (data, textStatus) {
|
||||
handleGenericAjaxResponse(data);
|
||||
},
|
||||
complete:function() {
|
||||
$(".loading").hide();
|
||||
$("#confirmation_box").fadeOut();
|
||||
var org = "/" + $('#org_id').text();
|
||||
$.get( "/sightings/listSightings/" + attribute_id + "/" + context + org, function(data) {
|
||||
$("#sightingsData").html(data);
|
||||
});
|
||||
},
|
||||
type:"post",
|
||||
cache: false,
|
||||
url:"/sightings/quickDelete/" + id,
|
||||
});
|
||||
}
|
||||
|
||||
function toggleSetting(e, setting, id) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
@ -732,6 +771,7 @@ function submitPopoverForm(context_id, referer, update_context_id) {
|
|||
var url = null;
|
||||
var context = 'event';
|
||||
var contextNamingConvention = 'Attribute';
|
||||
var closePopover = true;
|
||||
switch (referer) {
|
||||
case 'add':
|
||||
url = "/attributes/add/" + context_id;
|
||||
|
@ -778,20 +818,28 @@ function submitPopoverForm(context_id, referer, update_context_id) {
|
|||
case 'replaceAttributes':
|
||||
url = "/attributes/attributeReplace/" + context_id;
|
||||
break;
|
||||
case 'addSighting':
|
||||
url = "/sightings/add/" + context_id;
|
||||
closePopover = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (url !== null) {
|
||||
$.ajax({
|
||||
beforeSend: function (XMLHttpRequest) {
|
||||
$(".loading").show();
|
||||
$("#gray_out").fadeOut();
|
||||
$("#popover_form").fadeOut();
|
||||
if (closePopover) {
|
||||
$("#gray_out").fadeOut();
|
||||
$("#popover_form").fadeOut();
|
||||
}
|
||||
},
|
||||
data: $("#submitButton").closest("form").serialize(),
|
||||
success:function (data, textStatus) {
|
||||
var result = handleAjaxPopoverResponse(data, context_id, url, referer, context, contextNamingConvention);
|
||||
if (closePopover) {
|
||||
var result = handleAjaxPopoverResponse(data, context_id, url, referer, context, contextNamingConvention);
|
||||
}
|
||||
if (context == 'event' && (referer == 'add' || referer == 'massEdit' || referer == 'replaceAttributes')) eventUnpublish();
|
||||
$(".loading").show();
|
||||
$(".loading").hide();
|
||||
},
|
||||
type:"post",
|
||||
url:url
|
||||
|
@ -905,7 +953,10 @@ function showMessage(success, message, context) {
|
|||
}
|
||||
|
||||
function cancelPopoverForm() {
|
||||
$("#popover_form").empty();
|
||||
$("#gray_out").fadeOut();
|
||||
$("#popover_form").fadeOut();
|
||||
$("#screenshot_box").fadeOut();
|
||||
$("#confirmation_box").fadeOut();
|
||||
$('#gray_out').fadeOut();
|
||||
$('#popover_form').fadeOut();
|
||||
}
|
||||
|
@ -1108,6 +1159,7 @@ function getPopup(id, context, target, admin, popupType) {
|
|||
$(".loading").show();
|
||||
},
|
||||
dataType:"html",
|
||||
async: true,
|
||||
cache: false,
|
||||
success:function (data, textStatus) {
|
||||
$(".loading").hide();
|
||||
|
@ -1126,6 +1178,7 @@ function simplePopup(url) {
|
|||
$(".loading").show();
|
||||
},
|
||||
dataType:"html",
|
||||
async: true,
|
||||
cache: false,
|
||||
success:function (data, textStatus) {
|
||||
$(".loading").hide();
|
||||
|
@ -1926,7 +1979,6 @@ function simpleTabPageLast() {
|
|||
if ($('#SharingGroupRoaming').is(":checked")) {
|
||||
summaryservers = "any interconnected instances linked by an eligible organisation.";
|
||||
} else {
|
||||
console.log(servercounter);
|
||||
if (servercounter == 0) {
|
||||
summaryservers = "data marked with this sharing group will not be pushed.";
|
||||
}
|
||||
|
@ -2832,7 +2884,7 @@ function checkAndSetPublishedInfo() {
|
|||
|
||||
$(document).keyup(function(e){
|
||||
if (e.keyCode === 27) {
|
||||
$("#gray_out").fadeOut();
|
||||
$("#gray_out").fadeOut();
|
||||
$("#popover_form").fadeOut();
|
||||
$("#screenshot_box").fadeOut();
|
||||
$("#confirmation_box").fadeOut();
|
||||
|
@ -2845,3 +2897,9 @@ function closeScreenshot() {
|
|||
$("#screenshot_box").fadeOut();
|
||||
$("#gray_out").fadeOut();
|
||||
}
|
||||
|
||||
function loadSightingGraph(id, scope) {
|
||||
$.get( "/sightings/viewSightings/" + id + "/" + scope, function(data) {
|
||||
$("#sightingsData").html(data);
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue