(feat): Tags
fix(tag): Create tags on mobile chore(tags): Change TagName to Name chore(tags): eslint chore(tags): dbFetchAll to dbQuery for removetag chore(events): eslint (attempt 2) feat(tags): Better handling of keyboard fix(tags): Enter key for creating new tag fix(tags): Don't allow space as a tag name feat(tags): Delete tag if last assignment removed fix(tags): Increase height of dropdown in progress fix(Tags): Use T.Id on the events page dropdown fix(Tags): Remove $availableTags from events.php chore(sql): Formatting sql statements feat(Tags): Working OR on filters and events pages fix(filter): Populate availableTags chore(Tags): code formatting fix(tag): Add tag on create tag Fix(tags): Remove tag from available if last feat(tags): Add zm_update.sql fix(chosen): Undo css width fix(chosen): tags dropdown width fix(tags): dropdown over timeline fix(tags): Full width input fix(events): Refresh table on page show chore(filter): Clean up availableTags chore(event): Clean up available & selected Tags fix(event): Update available tags on remove fix(event): Remove hack for selected tags feat(tags): Blur input after adding tag doc(tags): Initial tags documentation fix(tags): Dark theme dropdown fix(tags): Dark theme for tags on input fix(tags): Dark theme for highlight in dropdown fix(tags): Populate filter tags droplist chore(): Bump zm_update to 1.37.42 chore(tags): Move mobile check to skin.js chore(tags): Comment debug statements fix(tags): Enter key to create tag on mobile Chome chore(tags): Space in 'All Tags' for translation Temporary commit to handle cookie expiration times chore(tags): Remove unnecessary Tag(s) from en_gb chore(): Cleanup unnecessary Error and Debug chore(): Resolve merge conflicts chore(): Address merge conflicts with masterpull/3758/head
parent
df411c3fe3
commit
18d74ed7ac
|
@ -0,0 +1,47 @@
|
|||
--
|
||||
-- This adds Tags
|
||||
--
|
||||
|
||||
SELECT 'Checking For Tags Table';
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE table_name = 'Tags'
|
||||
AND table_schema = DATABASE()
|
||||
) > 0,
|
||||
"SELECT 'Tags table exists'",
|
||||
"CREATE TABLE `Tags` (
|
||||
`Id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`Name` varchar(64) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL DEFAULT '',
|
||||
`CreateDate` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`CreatedBy` int(10) unsigned,
|
||||
`LastAssignedDate` dateTime,
|
||||
PRIMARY KEY (`Id`),
|
||||
UNIQUE(`Name`)
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
|
||||
SELECT 'Checking For Events_Tags Table';
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.TABLES
|
||||
WHERE table_name = 'Events_Tags'
|
||||
AND table_schema = DATABASE()
|
||||
) > 0,
|
||||
"SELECT 'Events_Tags table exists'",
|
||||
"CREATE TABLE `Events_Tags` (
|
||||
`TagId` bigint(20) unsigned NOT NULL,
|
||||
`EventId` bigint(20) unsigned NOT NULL,
|
||||
`AssignedDate` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
`AssignedBy` int(10) unsigned,
|
||||
PRIMARY KEY (`TagId`, `EventId`),
|
||||
CONSTRAINT `Events_Tags_ibfk_1` FOREIGN KEY (`TagId`) REFERENCES `Tags` (`Id`) ON DELETE CASCADE,
|
||||
CONSTRAINT `Events_Tags_ibfk_2` FOREIGN KEY (`EventId`) REFERENCES `Events` (`Id`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
|
@ -11,6 +11,7 @@ User Guide
|
|||
viewmonitors
|
||||
filterevents
|
||||
viewevents
|
||||
tags
|
||||
options
|
||||
cameracontrol
|
||||
mobile
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
Tags
|
||||
====
|
||||
|
||||
Tags are a simple quick way to categorize events so that you can identify them easier.
|
||||
|
||||
|
||||
Creating New Tags
|
||||
-----------------
|
||||
Creating new tags is as easy as typing a word in the tags field (located just above the video). Pressing the space bar, comma, or Enter will create the new tag and add it to the event.
|
||||
|
||||
|
||||
Adding Existing Tags to an Event
|
||||
--------------------------------
|
||||
Clicking in the tags field will show a dropdown list of all of the available tags in descending order of when they were last added to an event.
|
||||
|
||||
An existing tag can be added to the event by clicking it from the dropdown or by using the down/up arrow keys to highlight the desired tag and pressing Enter.
|
||||
|
||||
<Ctrl-Down Arrow> will add the tag most recently added to any event to the current event.
|
||||
|
||||
Typing in the tags field will filter the available tags to the ones that contain the text typed.
|
||||
|
||||
.. note::
|
||||
Since you can use the right/left arrows to move between events when the tags field doesn't have focus, you can quickly add the most recent tag with <Ctrl-Down Arrow> and then move to the next event with Right Arrow. You can also use the Down Arrow to bring up the available tags to add a different tag before pressing the Right Arrow to move to the next event.
|
||||
|
||||
|
||||
Removing Tags from an Event
|
||||
---------------------------
|
||||
Pressing the "x" to the right of a tag will remove it from the event. When the tag is removed from the last event, the tag will be deleted from the available tags.
|
||||
|
||||
|
||||
Filtering with Tags
|
||||
===================
|
||||
Current Limitations
|
||||
-------------------
|
||||
1. Filtering for multiple tags is an OR search (Goal is to make this an AND search)
|
||||
2. Resulting events only display the tags that were searched (Goal is to display all of the tags on the resulting events)
|
||||
3. There is no way to search for events that don't have any tag (Goal is to provide search criteria for events with no tag)
|
||||
4. There is no way to search for events with ONLY the specified tag or tags (Goal is to provide search criteria to search for events with ONLY the specified tag or tags)
|
|
@ -143,8 +143,21 @@ sub Sql {
|
|||
}
|
||||
|
||||
my $filter_expr = ZoneMinder::General::jsonDecode($self->{Query_json});
|
||||
my $sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time
|
||||
FROM Events as E';
|
||||
my $sql = '
|
||||
SELECT
|
||||
E.*,
|
||||
unix_timestamp(E.StartDateTime)
|
||||
AS Time,
|
||||
GROUP_CONCAT(T.Name SEPARATOR ", ")
|
||||
FROM Events
|
||||
AS E
|
||||
LEFT JOIN Events_Tags
|
||||
AS ET
|
||||
ON E.Id = ET.EventId
|
||||
LEFT JOIN Tags
|
||||
AS T
|
||||
ON T.Id = ET.TagId
|
||||
';
|
||||
|
||||
if ( $filter_expr->{terms} ) {
|
||||
foreach my $term ( @{$filter_expr->{terms}} ) {
|
||||
|
@ -164,6 +177,8 @@ sub Sql {
|
|||
|
||||
if ( $term->{attr} eq 'AlarmedZoneId' ) {
|
||||
$term->{op} = 'EXISTS';
|
||||
} elsif ( $term->{attr} eq 'Tags' ) {
|
||||
$self->{Sql} .= 'T.Name';
|
||||
} elsif ( $term->{attr} =~ /^Monitor/ ) {
|
||||
$sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName
|
||||
FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId';
|
||||
|
@ -368,6 +383,8 @@ sub Sql {
|
|||
my $sort_column = '';
|
||||
if ( $filter_expr->{sort_field} eq 'Id' ) {
|
||||
$sort_column = 'E.Id';
|
||||
} elsif ( $filter_expr->{sort_field} eq 'Tag' ) {
|
||||
$sort_column = 'T.Name';
|
||||
} elsif ( $filter_expr->{sort_field} eq 'MonitorName' ) {
|
||||
$sql = 'SELECT E.*, unix_timestamp(E.StartDateTime) as Time, M.Name as MonitorName
|
||||
FROM Events as E INNER JOIN Monitors as M on M.Id = E.MonitorId';
|
||||
|
|
|
@ -15,7 +15,26 @@ if ( canView('Events') or canView('Snapshots') ) {
|
|||
} elseif ( empty($_REQUEST['scale']) ) {
|
||||
ajaxError('Video Generation Failure, no scale given');
|
||||
} else {
|
||||
$sql = 'SELECT E.*,M.Name AS MonitorName,M.DefaultRate,M.DefaultScale FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE E.Id = ?'.monitorLimitSql();
|
||||
$sql = '
|
||||
SELECT
|
||||
E.*,
|
||||
M.Name
|
||||
AS MonitorName,M.DefaultRate,M.DefaultScale,
|
||||
GROUP_CONCAT(T.Name SEPARATOR ", ")
|
||||
AS Tags
|
||||
FROM Events
|
||||
AS E
|
||||
INNER JOIN Monitors
|
||||
AS M
|
||||
ON E.MonitorId = M.Id
|
||||
LEFT JOIN Events_Tags
|
||||
AS ET
|
||||
ON E.Id = ET.EventId
|
||||
LEFT JOIN Tags
|
||||
AS T
|
||||
ON T.Id = ET.TagId
|
||||
WHERE
|
||||
E.Id = ?'.monitorLimitSql();
|
||||
if ( !($event = dbFetchOne($sql, NULL, array( $_REQUEST['id']))) ) {
|
||||
ajaxError('Video Generation Failure, Unable to load event');
|
||||
} else {
|
||||
|
@ -167,6 +186,45 @@ if ( canEdit('Events') ) {
|
|||
ajaxResponse(array('refreshEvent'=>false, 'refreshParent'=>true));
|
||||
}
|
||||
break;
|
||||
case 'getselectedtags' :
|
||||
$sql = '
|
||||
SELECT
|
||||
T.*
|
||||
FROM Tags
|
||||
AS T
|
||||
INNER JOIN Events_Tags
|
||||
AS ET
|
||||
ON ET.TagId = T.Id
|
||||
WHERE ET.EventId = ?
|
||||
';
|
||||
$values = array($_REQUEST['id']);
|
||||
$response = dbFetchAll($sql, NULL, $values);
|
||||
ajaxResponse(array('response'=>$response));
|
||||
break;
|
||||
case 'addtag' :
|
||||
$sql = 'INSERT INTO Events_Tags (TagId, EventId, AssignedBy) VALUES (?, ?, ?)';
|
||||
$values = array($_REQUEST['tid'], $_REQUEST['id'], $user->Id());
|
||||
$response = dbFetchAll($sql, NULL, $values);
|
||||
|
||||
$sql = 'UPDATE Tags SET LastAssignedDate = NOW() WHERE Id = ?';
|
||||
$values = array($_REQUEST['tid']);
|
||||
dbFetchAll($sql, NULL, $values);
|
||||
|
||||
ajaxResponse(array('response'=>$response));
|
||||
break;
|
||||
case 'removetag' :
|
||||
$tagId = $_REQUEST['tid'];
|
||||
dbQuery('DELETE FROM Events_Tags WHERE TagId = ? AND EventId = ?', array($tagId, $_REQUEST['id']));
|
||||
$sql = "SELECT * FROM Events_Tags WHERE TagId = $tagId";
|
||||
$rowCount = dbNumRows($sql);
|
||||
if ($rowCount < 1) {
|
||||
$sql = 'DELETE FROM Tags WHERE Id = ?';
|
||||
$values = array($_REQUEST['tid']);
|
||||
$response = dbNumRows($sql, $values);
|
||||
ajaxResponse(array('response'=>$response));
|
||||
}
|
||||
ajaxResponse();
|
||||
break;
|
||||
} // end switch action
|
||||
} // end if canEdit('Events')
|
||||
|
||||
|
|
|
@ -32,7 +32,10 @@ require_once('includes/Filter.php');
|
|||
$filter = isset($_REQUEST['filter']) ? ZM\Filter::parse($_REQUEST['filter']) : new ZM\Filter();
|
||||
if (count( $user->unviewableMonitorIds())) {
|
||||
$filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>$user->viewableMonitorIds()));
|
||||
// $filter = $filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=>'IN', 'val'=>'5'));
|
||||
}
|
||||
// TODO: Why is $user->viewableMonitorIds() returning $user->unviewableMonitorIds()
|
||||
// Error('$user->viewableMonitorIds(): '.print_r($user->viewableMonitorIds()));
|
||||
if (!empty($_REQUEST['StartDateTime'])) {
|
||||
$filter->addTerm(array('cnj'=>'and', 'attr'=>'StartDateTime', 'op'=> '>=', 'val'=>$_REQUEST['StartDateTime']));
|
||||
}
|
||||
|
@ -42,6 +45,9 @@ if (!empty($_REQUEST['EndDateTime'])) {
|
|||
if (!empty($_REQUEST['MonitorId'])) {
|
||||
$filter->addTerm(array('cnj'=>'and', 'attr'=>'MonitorId', 'op'=> '=', 'val'=>$_REQUEST['MonitorId']));
|
||||
}
|
||||
if (!empty($_REQUEST['Tag'])) {
|
||||
$filter->addTerm(array('cnj'=>'and', 'attr'=>'Tag', 'op'=>'=', 'val'=>''));
|
||||
}
|
||||
|
||||
// Search contains a user entered string to search on
|
||||
$search = isset($_REQUEST['search']) ? $_REQUEST['search'] : '';
|
||||
|
@ -176,12 +182,14 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
|
|||
$columns = array('Id', 'MonitorId', 'StorageId', 'Name', 'Cause', 'StartDateTime', 'EndDateTime', 'Length', 'Frames', 'AlarmFrames', 'TotScore', 'AvgScore', 'MaxScore', 'Archived', 'Emailed', 'Notes', 'DiskSpace');
|
||||
|
||||
// The names of columns shown in the event view that are NOT dB columns in the database
|
||||
$col_alt = array('Monitor', 'Storage');
|
||||
$col_alt = array('Monitor', 'Tags', 'Storage');
|
||||
|
||||
if ( $sort != '' ) {
|
||||
if (!in_array($sort, array_merge($columns, $col_alt))) {
|
||||
ZM\Error('Invalid sort field: ' . $sort);
|
||||
$sort = '';
|
||||
} else if ( $sort == 'Tags' ) {
|
||||
$sort = 'T.Name';
|
||||
} else if ( $sort == 'Monitor' ) {
|
||||
$sort = 'M.Name';
|
||||
} else if ($sort == 'EndDateTime') {
|
||||
|
@ -197,13 +205,46 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
|
|||
|
||||
$values = array();
|
||||
$likes = array();
|
||||
// Error($filter->sql());
|
||||
$where = $filter->sql()?' WHERE ('.$filter->sql().')' : '';
|
||||
|
||||
$col_str = 'E.*, UNIX_TIMESTAMP(E.StartDateTime) AS StartTimeSecs,
|
||||
CASE WHEN E.EndDateTime IS NULL THEN (SELECT NOW()) ELSE E.EndDateTime END AS EndDateTime,
|
||||
CASE WHEN E.EndDateTime IS NULL THEN (SELECT UNIX_TIMESTAMP(NOW())) ELSE UNIX_TIMESTAMP(EndDateTime) END AS EndTimeSecs,
|
||||
M.Name AS Monitor';
|
||||
$sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'.$where.($sort?' ORDER BY '.$sort.' '.$order:'');
|
||||
$col_str = '
|
||||
E.*,
|
||||
UNIX_TIMESTAMP(E.StartDateTime)
|
||||
AS StartTimeSecs,
|
||||
CASE WHEN E.EndDateTime
|
||||
IS NULL
|
||||
THEN (SELECT NOW())
|
||||
ELSE E.EndDateTime END
|
||||
AS EndDateTime,
|
||||
CASE WHEN E.EndDateTime
|
||||
IS NULL
|
||||
THEN (SELECT UNIX_TIMESTAMP(NOW()))
|
||||
ELSE UNIX_TIMESTAMP(EndDateTime) END
|
||||
AS EndTimeSecs,
|
||||
M.Name
|
||||
AS Monitor,
|
||||
GROUP_CONCAT(T.Name SEPARATOR ", ")
|
||||
AS Tags';
|
||||
|
||||
$sql = '
|
||||
SELECT
|
||||
' .$col_str. '
|
||||
FROM `Events`
|
||||
AS E
|
||||
INNER JOIN Monitors
|
||||
AS M
|
||||
ON E.MonitorId = M.Id
|
||||
LEFT JOIN Events_Tags
|
||||
AS ET
|
||||
ON E.Id = ET.EventId
|
||||
LEFT JOIN Tags
|
||||
AS T
|
||||
ON T.Id = ET.TagId
|
||||
'.$where.'
|
||||
GROUP BY E.Id
|
||||
'.($sort?' ORDER BY '.$sort.' '.$order:'');
|
||||
|
||||
if ($filter->limit() and !count($filter->post_sql_conditions())) {
|
||||
$sql .= ' LIMIT '.$filter->limit();
|
||||
}
|
||||
|
@ -243,6 +284,8 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
|
|||
|
||||
$filtered_rows = null;
|
||||
|
||||
ZM\Debug('$advsearch: ' . $advsearch );
|
||||
ZM\Debug('$search: ' . $search);
|
||||
if (count($advsearch) or $search != '') {
|
||||
$search_filter = new ZM\Filter();
|
||||
$search_filter = $search_filter->addTerm(array('cnj'=>'and', 'attr'=>'Id', 'op'=>'IN', 'val'=>$event_ids));
|
||||
|
@ -270,7 +313,24 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
|
|||
$search_filter = $search_filter->addTerms($terms, array('obr'=>1, 'cbr'=>1, 'op'=>'OR'));
|
||||
} # end if search
|
||||
|
||||
$sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE '.$search_filter->sql().' ORDER BY ' .$sort. ' ' .$order;
|
||||
$sql = 'SELECT ' .$col_str. '
|
||||
FROM `Events`
|
||||
AS E
|
||||
INNER JOIN Monitors
|
||||
AS M
|
||||
ON E.MonitorId = M.Id
|
||||
LEFT JOIN Events_Tags
|
||||
AS ET
|
||||
ON E.Id = ET.EventId
|
||||
LEFT JOIN Tags
|
||||
AS T
|
||||
ON T.Id = ET.TagId
|
||||
WHERE
|
||||
'.$search_filter->sql().'
|
||||
ORDER BY
|
||||
' .$sort. '
|
||||
' .$order;
|
||||
|
||||
$filtered_rows = dbFetchAll($sql);
|
||||
ZM\Debug('Have ' . count($filtered_rows) . ' events matching search filter: '.$sql);
|
||||
} else {
|
||||
|
@ -303,6 +363,7 @@ function queryRequest($filter, $search, $advsearch, $sort, $offset, $order, $lim
|
|||
$row['Archived'] = $row['Archived'] ? translate('Yes') : translate('No');
|
||||
$row['Emailed'] = $row['Emailed'] ? translate('Yes') : translate('No');
|
||||
$row['Cause'] = validHtmlStr($row['Cause']);
|
||||
$row['Tags'] = validHtmlStr($row['Tags']);
|
||||
$row['StartDateTime'] = $dateTimeFormatter->format(strtotime($row['StartDateTime']));
|
||||
$row['EndDateTime'] = $row['EndDateTime'] ? $dateTimeFormatter->format(strtotime($row['EndDateTime'])) : null;
|
||||
$row['Storage'] = ( $row['StorageId'] and isset($StorageById[$row['StorageId']]) ) ? $StorageById[$row['StorageId']]->Name() : 'Default';
|
||||
|
|
|
@ -472,7 +472,25 @@ function getNearEvents() {
|
|||
$sortOrder = 'ASC';
|
||||
}
|
||||
|
||||
$sql = 'SELECT E.Id AS Id, E.StartDateTime AS StartDateTime FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE '.$sortColumn.' '.($sortOrder=='ASC'?'<=':'>=').' \''.$event[$_REQUEST['sort_field']].'\'';
|
||||
$sql = '
|
||||
SELECT
|
||||
E.Id
|
||||
AS Id,
|
||||
E.StartDateTime
|
||||
AS StartDateTime
|
||||
FROM Events
|
||||
AS E
|
||||
INNER JOIN Monitors
|
||||
AS M
|
||||
ON E.MonitorId = M.Id
|
||||
LEFT JOIN Events_Tags
|
||||
AS ET
|
||||
ON E.Id = ET.EventId
|
||||
LEFT JOIN Tags
|
||||
AS T
|
||||
ON T.Id = ET.TagId
|
||||
WHERE '.$sortColumn.'
|
||||
'.($sortOrder=='ASC'?'<=':'>=').' \''.$event[$_REQUEST['sort_field']].'\'';
|
||||
if ($filter->sql()) {
|
||||
$sql .= ' AND ('.$filter->sql().')';
|
||||
}
|
||||
|
@ -490,7 +508,25 @@ function getNearEvents() {
|
|||
|
||||
$prevEvent = dbFetchNext($result);
|
||||
|
||||
$sql = 'SELECT E.Id AS Id, E.StartDateTime AS StartDateTime FROM Events AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id WHERE '.$sortColumn .' '.($sortOrder=='ASC'?'>=':'<=').' \''.$event[$_REQUEST['sort_field']].'\'';
|
||||
$sql = '
|
||||
SELECT
|
||||
E.Id
|
||||
AS Id,
|
||||
E.StartDateTime
|
||||
AS StartDateTime
|
||||
FROM Events
|
||||
AS E
|
||||
INNER JOIN Monitors
|
||||
AS M
|
||||
ON E.MonitorId = M.Id
|
||||
LEFT JOIN Events_Tags
|
||||
AS ET
|
||||
ON E.Id = ET.EventId
|
||||
LEFT JOIN Tags
|
||||
AS T
|
||||
ON T.Id = ET.TagId
|
||||
WHERE '.$sortColumn.'
|
||||
'.($sortOrder=='ASC'?'>=':'<=').' \''.$event[$_REQUEST['sort_field']].'\'';
|
||||
if ($filter->sql()) {
|
||||
$sql .= ' AND ('.$filter->sql().')';
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
switch ( $_REQUEST['action'] ) {
|
||||
case 'getavailabletags' :
|
||||
$sql = 'SELECT * FROM Tags ORDER BY LastAssignedDate DESC';
|
||||
$dbFetchResult = dbFetchAll($sql);
|
||||
ajaxResponse(array('response'=>$dbFetchResult));
|
||||
break;
|
||||
case 'createtag' :
|
||||
$sql = 'INSERT INTO Tags (Name, CreatedBy) VALUES (?, ?) RETURNING Id';
|
||||
$values = array($_REQUEST['tname'], $user->Id());
|
||||
$result = dbFetchAll($sql, NULL, $values);
|
||||
$r = $result[0];
|
||||
|
||||
$sql = 'SELECT * FROM Tags WHERE Id = ?';
|
||||
$values = array($r['Id']);
|
||||
$dbFetchResult = dbFetchAll($sql, NULL, $values);
|
||||
|
||||
ajaxResponse(array('response'=>$dbFetchResult));
|
||||
break;
|
||||
} // end switch action
|
||||
|
||||
ajaxError('Unrecognised action '.$_REQUEST['action']);
|
||||
?>
|
|
@ -47,8 +47,29 @@ $order = (isset($_REQUEST['order']) and (strtolower($_REQUEST['order']) == 'asc'
|
|||
//
|
||||
|
||||
$where = 'WHERE MonitorId = '.$mid;
|
||||
$col_str = 'E.*';
|
||||
$sql = 'SELECT ' .$col_str. ' FROM `Events` AS E '.$where.' ORDER BY '.$sort.' '.$order. ' LIMIT ?';
|
||||
|
||||
$col_str = '
|
||||
E.*,
|
||||
T.Name
|
||||
AS Tags ';
|
||||
|
||||
$sql = '
|
||||
SELECT
|
||||
' .$col_str. '
|
||||
FROM `Events`
|
||||
AS E
|
||||
LEFT JOIN Events_Tags
|
||||
AS ET
|
||||
ON E.Id = ET.EventId
|
||||
LEFT JOIN Tags
|
||||
AS T
|
||||
ON T.Id = ET.TagId
|
||||
'.$where.'
|
||||
ORDER BY
|
||||
'.$sort.'
|
||||
'.$order.'
|
||||
LIMIT ?';
|
||||
|
||||
ZM\Debug('Calling the following sql query: ' .$sql);
|
||||
$rows = dbQuery($sql, array($limit));
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ class Event extends ZM_Object {
|
|||
'StorageId' => null,
|
||||
'SecondaryStorageId' => null,
|
||||
'Cause' => '',
|
||||
'Tags' => array(),
|
||||
'StartDateTime' => null,
|
||||
'EndDateTime' => null,
|
||||
'Width' => null,
|
||||
|
|
|
@ -11,6 +11,7 @@ class Event_Data extends ZM_Object {
|
|||
'EventId' => null,
|
||||
'FrameId' => null,
|
||||
'MonitorId' => null,
|
||||
'Tags' => array(),
|
||||
'TimeStamp' => 0,
|
||||
'Data' => '',
|
||||
);
|
||||
|
|
|
@ -47,12 +47,19 @@ class Filter extends ZM_Object {
|
|||
public $_pre_sql_conditions;
|
||||
public $_post_sql_conditions;
|
||||
protected $_Terms;
|
||||
public $availableTags = array();
|
||||
|
||||
public function sql() {
|
||||
// Debug('$_Terms: '. $_Terms);
|
||||
// Debug('$_sql: ' . $_sql);
|
||||
// Debug('$this->_sql: ' . $this->_sql);
|
||||
if (!isset($this->_sql)) {
|
||||
$this->_sql = '';
|
||||
foreach ( $this->FilterTerms() as $term ) {
|
||||
// Error($term->valid());
|
||||
if ($term->valid()) {
|
||||
// Debug('$this->_sql: ' . $this->_sql);
|
||||
// Error($this-cnj);
|
||||
if (!$this->_sql) {
|
||||
if ($term->cnj) unset($term->cnj);
|
||||
} else {
|
||||
|
@ -60,10 +67,12 @@ class Filter extends ZM_Object {
|
|||
}
|
||||
$this->_sql .= $term->sql();
|
||||
} else {
|
||||
Debug('Term is not valid '.$term->to_string());
|
||||
// Debug('Term is not valid '.$term->to_string());
|
||||
}
|
||||
// Debug('$term->_sql: ' . $term->_sql);
|
||||
} # end foreach term
|
||||
}
|
||||
// Debug('$this->_sql: ' . $this->_sql);
|
||||
return $this->_sql;
|
||||
}
|
||||
|
||||
|
@ -118,18 +127,24 @@ class Filter extends ZM_Object {
|
|||
}
|
||||
|
||||
public function FilterTerms() {
|
||||
// echo '<pre style="text-align: start;">Terms before: '; print_r($this->Terms); echo '</pre>';
|
||||
if (!isset($this->Terms)) {
|
||||
$this->Terms = array();
|
||||
$_terms = $this->terms();
|
||||
// echo '<pre style="text-align: start;">$_terms: '; print_r($_terms); echo '</pre>';
|
||||
if ($_terms) {
|
||||
for ($i=0; $i < count($_terms); $i++) {
|
||||
// Error($i);
|
||||
// Error($_terms[$i]);
|
||||
if (isset($_terms[$i])) {
|
||||
$term = new FilterTerm($this, $_terms[$i], $i);
|
||||
$this->Terms[] = $term;
|
||||
// Error($this->Terms[]);
|
||||
}
|
||||
} # end foreach term
|
||||
}
|
||||
}
|
||||
// echo '<pre style="text-align: start;">Terms after: '; print_r($this->Terms); echo '</pre>';
|
||||
return $this->Terms;
|
||||
}
|
||||
|
||||
|
@ -260,6 +275,7 @@ class Filter extends ZM_Object {
|
|||
$Query['limit'] = func_get_arg(0);
|
||||
$this->Query($Query);
|
||||
}
|
||||
// Error($this->Query()['limit']);
|
||||
if ( isset( $this->Query()['limit'] ) )
|
||||
return $this->{'Query'}['limit'];
|
||||
return 0;
|
||||
|
@ -414,8 +430,12 @@ class Filter extends ZM_Object {
|
|||
}
|
||||
}
|
||||
if ( !empty($term['attr']) ) {
|
||||
// Error($term['attr']);
|
||||
$dtAttr = false;
|
||||
switch ( $term['attr']) {
|
||||
case 'Tags':
|
||||
$sqlValue = 'T.Name';
|
||||
break;
|
||||
case 'Group':
|
||||
$sqlValue = 'M.Id';
|
||||
case 'Monitor':
|
||||
|
@ -586,6 +606,8 @@ class Filter extends ZM_Object {
|
|||
case 'Group':
|
||||
$value = Group::get_group_sql($value);
|
||||
break;
|
||||
case 'Tags':
|
||||
// Error($term['attr']);
|
||||
case 'MonitorName':
|
||||
case 'Name':
|
||||
case 'Cause':
|
||||
|
@ -719,10 +741,30 @@ class Filter extends ZM_Object {
|
|||
}
|
||||
|
||||
$where = $this->sql() ? ' WHERE ('.$this->sql().')' : '';
|
||||
// $where = ' WHERE ( T.Name = "Bird" )';
|
||||
$sort = $this->sort_field() ? $this->sort_field() .' '.($this->sort_asc() ? 'ASC' : 'DESC') : '';
|
||||
|
||||
$col_str = 'E.*, M.Name AS Monitor';
|
||||
$sql = 'SELECT ' .$col_str. ' FROM `Events` AS E INNER JOIN Monitors AS M ON E.MonitorId = M.Id'.$where.($sort?' ORDER BY '.$sort:'');
|
||||
$col_str = '
|
||||
E.*,
|
||||
M.Name
|
||||
AS Monitor';
|
||||
|
||||
$sql = '
|
||||
SELECT
|
||||
' .$col_str. '
|
||||
FROM `Events`
|
||||
AS E
|
||||
INNER JOIN Monitors
|
||||
AS M
|
||||
ON E.MonitorId = M.Id
|
||||
LEFT JOIN Events_Tags
|
||||
AS ET
|
||||
ON E.Id = ET.EventId
|
||||
LEFT JOIN Tags
|
||||
AS T
|
||||
ON T.Id = ET.TagId
|
||||
'.$where.($sort?' ORDER BY '.$sort:'');
|
||||
|
||||
if ($this->limit() and !count($this->pre_sql_conditions()) and !count($this->post_sql_conditions())) {
|
||||
$sql .= ' LIMIT '.$this->limit();
|
||||
}
|
||||
|
@ -785,6 +827,7 @@ class Filter extends ZM_Object {
|
|||
'StorageId' => translate('AttrStorageArea'),
|
||||
'StorageServerId' => translate('AttrStorageServer'),
|
||||
'SystemLoad' => translate('AttrSystemLoad'),
|
||||
'Tags' => translate('Tags'),
|
||||
'TotScore' => translate('AttrTotalScore'),
|
||||
);
|
||||
}
|
||||
|
@ -896,6 +939,10 @@ class Filter extends ZM_Object {
|
|||
}
|
||||
}
|
||||
}
|
||||
// $availableTags = array();
|
||||
foreach ( dbFetchAll('SELECT Id, Name FROM Tags ORDER BY LastAssignedDate DESC') AS $tag ) {
|
||||
$availableTags[$tag['Id']] = validHtmlStr($tag['Name']);
|
||||
}
|
||||
|
||||
for ($i=0; $i < count($terms); $i++) {
|
||||
$term = $terms[$i];
|
||||
|
@ -919,6 +966,23 @@ class Filter extends ZM_Object {
|
|||
if ( $term['attr'] == 'Archived' ) {
|
||||
$html .= '<td>'.translate('OpEq').'<input type="hidden" name="filter[Query][terms]['.$i.'][op]" value="="/></td>'.PHP_EOL;
|
||||
$html .= '<td>'.htmlSelect("filter[Query][terms][$i][val]", $archiveTypes, $term['val']).'</td>'.PHP_EOL;
|
||||
|
||||
|
||||
|
||||
} else if ( $term['attr'] == 'Tags') {
|
||||
// Error($term['attr']);
|
||||
$html .= '<td>'.htmlSelect("filter[Query][terms][$i][op]", $opTypes, $term['op']).'</td>'.PHP_EOL;
|
||||
$options = ['class'=>'chosen', 'multiple'=>'multiple'];
|
||||
$selected = explode(',', $term['val']);
|
||||
if (count($selected) == 1 and !$selected[0]) {
|
||||
$selected = null;
|
||||
}
|
||||
$html .= '<td>'.htmlSelect("filter[Query][terms][$i][val]", $availableTags, $selected, $options).'</td>'.PHP_EOL;
|
||||
// ZM\Debug('$availableTags: '.$availableTags);
|
||||
// ZM\Debug('$selected: '.$selected);
|
||||
|
||||
|
||||
|
||||
} else if ( $term['attr'] == 'DateTime' || $term['attr'] == 'StartDateTime' || $term['attr'] == 'EndDateTime') {
|
||||
$html .= '<td>'.htmlSelect("filter[Query][terms][$i][op]", $opTypes, $term['op']).'</td>'.PHP_EOL;
|
||||
$html .= '<td><input type="text" name="filter[Query][terms]['.$i.'][val]" id="filter[Query][terms]['.$i.'][val]" value="'.(isset($term['val'])?validHtmlStr(str_replace('T', ' ', $term['val'])):'').'"/></td>'.PHP_EOL;
|
||||
|
@ -983,6 +1047,7 @@ class Filter extends ZM_Object {
|
|||
</tr>
|
||||
';
|
||||
} # end foreach term
|
||||
// Error($html);
|
||||
return $html;
|
||||
} # end function widget()
|
||||
|
||||
|
@ -1006,6 +1071,10 @@ class Filter extends ZM_Object {
|
|||
foreach ( $Servers as $server ) {
|
||||
$servers[$server->Id()] = validHtmlStr($server->Name());
|
||||
}
|
||||
// $availableTags = array();
|
||||
foreach ( dbFetchAll('SELECT Id, Name FROM Tags ORDER BY LastAssignedDate DESC') AS $tag ) {
|
||||
$availableTags[$tag['Id']] = validHtmlStr($tag['Name']);
|
||||
}
|
||||
|
||||
for ($i=0; $i < count($terms); $i++) {
|
||||
$term = $terms[$i];
|
||||
|
@ -1034,6 +1103,39 @@ class Filter extends ZM_Object {
|
|||
if ( $term['attr'] == 'Archived' ) {
|
||||
$html .= htmlSelect("filter[Query][terms][$i][val]", $archiveTypes, $term['val']).PHP_EOL;
|
||||
|
||||
|
||||
|
||||
|
||||
} else if ( $term['attr'] == 'Tags' ) {
|
||||
$selected = explode(',', $term['val']);
|
||||
// echo '<pre>selected: '; print_r($selected); echo '</pre>';
|
||||
if (count($selected) == 1 and !$selected[0]) {
|
||||
$selected = null;
|
||||
}
|
||||
$options = ['class'=>'chosen', 'multiple'=>'multiple', 'data-placeholder'=>translate('All Tags')];
|
||||
if (isset($term['cookie'])) {
|
||||
$options['data-cookie'] = $term['cookie'];
|
||||
|
||||
if (!$selected and isset($_COOKIE[$term['cookie']]) and $_COOKIE[$term['cookie']])
|
||||
$selected = explode(',', $_COOKIE[$term['cookie']]);
|
||||
}
|
||||
// These echo statements print these variables at the top of the view.
|
||||
// echo '<pre>availableTags: '; print_r($availableTags); echo '</pre>';
|
||||
// echo '<pre>selected: '; print_r($selected); echo '</pre>';
|
||||
// echo '<pre>options: '; print_r($options); echo '</pre>';
|
||||
|
||||
$html .= '<span>'.htmlSelect("filter[Query][terms][$i][val]", $availableTags, $selected, $options).'</span>'.PHP_EOL;
|
||||
// $html .= '<span>'.htmlSelect("filter[Query][terms][$i][val]", array_combine($availableTags,$availableTags), $term['val'],
|
||||
// $options).'</span>'.PHP_EOL;
|
||||
// $html .= '<span>'.htmlSelect("filter[Query][terms][$i][val]", $availableTags, $term['val'], $options).'</span>'.PHP_EOL;
|
||||
|
||||
// Debug doesn't work here.
|
||||
// Debug('$availableTags: '.$availableTags);
|
||||
// Debug('$selected: '.$selected);
|
||||
// Debug('$options: '.$options);
|
||||
|
||||
|
||||
|
||||
} else if ( $term['attr'] == 'DateTime' || $term['attr'] == 'StartDateTime' || $term['attr'] == 'EndDateTime') {
|
||||
$html .= '<span><input type="text" class="datetimepicker" name="filter[Query][terms]['.$i.'][val]"';
|
||||
if (isset($term['id'])) {
|
||||
|
@ -1079,6 +1181,7 @@ class Filter extends ZM_Object {
|
|||
}
|
||||
}
|
||||
$selected = explode(',', $term['val']);
|
||||
// echo '<pre>Monitor selected: '; print_r($selected); echo '</pre>';
|
||||
if (count($selected) == 1 and !$selected[0]) {
|
||||
$selected = null;
|
||||
}
|
||||
|
@ -1145,8 +1248,9 @@ class Filter extends ZM_Object {
|
|||
$html .= '</span>';
|
||||
} # end foreach term
|
||||
$html .= '</div>';
|
||||
// Error($html);
|
||||
return $html;
|
||||
} # end function widget()
|
||||
} # end function simple_widget()
|
||||
|
||||
public function has_term($attr, $op=null) {
|
||||
foreach ($this->terms() as $term) {
|
||||
|
|
|
@ -101,6 +101,7 @@ class FilterTerm {
|
|||
case 'DiskPercent':
|
||||
$value = '';
|
||||
break;
|
||||
case 'Tags':
|
||||
case 'MonitorName':
|
||||
case 'Name':
|
||||
case 'Cause':
|
||||
|
@ -293,6 +294,8 @@ class FilterTerm {
|
|||
case 'StateId':
|
||||
case 'Archived':
|
||||
return $this->tablename.'.'.$this->attr;
|
||||
case 'Tags':
|
||||
return 'T.Id';
|
||||
default :
|
||||
return $this->tablename.'.'.$this->attr;
|
||||
}
|
||||
|
@ -444,6 +447,11 @@ class FilterTerm {
|
|||
Error('Failed evaluating '.$string_to_eval);
|
||||
return false;
|
||||
}
|
||||
} else if ( $this->attr == 'Tags' ) {
|
||||
// Debug('TODO: Complete this post_sql_condition for Tags val: ' . $this->val . ' op: ' . $this->op . ' id: ' . $this->id);
|
||||
// Debug(print_r($this, true));
|
||||
// Debug(print_r($event, true));
|
||||
return true;
|
||||
} else {
|
||||
Error('testing unsupported post term ' . $this->attr);
|
||||
}
|
||||
|
@ -460,7 +468,7 @@ class FilterTerm {
|
|||
}
|
||||
|
||||
public function is_post_sql() {
|
||||
if ( $this->attr == 'ExistsInFileSystem' ) {
|
||||
if ( $this->attr == 'ExistsInFileSystem' || $this->attr == 'Tags') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -515,6 +523,7 @@ class FilterTerm {
|
|||
'Notes',
|
||||
'StateId',
|
||||
'Archived',
|
||||
'Tags',
|
||||
# The following are for snapshots
|
||||
'CreatedOn',
|
||||
'Description'
|
||||
|
@ -536,6 +545,7 @@ class FilterTerm {
|
|||
return false;
|
||||
break;
|
||||
case 'Archived' :
|
||||
case 'Tags' :
|
||||
case 'Monitor' :
|
||||
case 'MonitorId' :
|
||||
case 'ServerId' :
|
||||
|
|
|
@ -224,8 +224,8 @@ function dbFetchNext($result, $col=false) {
|
|||
return false;
|
||||
}
|
||||
|
||||
function dbNumRows( $sql ) {
|
||||
$result = dbQuery($sql);
|
||||
function dbNumRows($sql, $params=NULL) {
|
||||
$result = dbQuery($sql, $params);
|
||||
return $result->rowCount();
|
||||
}
|
||||
|
||||
|
|
|
@ -709,6 +709,7 @@ ul.nav.nav-pills.flex-column {
|
|||
|
||||
.chosen-container {
|
||||
text-align: left;
|
||||
min-width: 11em;
|
||||
}
|
||||
|
||||
.chosen-single,
|
||||
|
@ -884,3 +885,84 @@ a.flip {
|
|||
button .material-icons {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
/* input[type="search"]::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
} */
|
||||
|
||||
.tags-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
border: 1px solid;
|
||||
border-color: #ccc;
|
||||
border-radius: 4px;
|
||||
min-height: 35px;
|
||||
margin: 0.25rem 1rem 0.25rem 1rem;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background-color: #F0F0F0;
|
||||
border-radius: 12px;
|
||||
padding: 4px 8px;
|
||||
margin: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tag-text {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.tag-remove {
|
||||
cursor: pointer;
|
||||
color: red;
|
||||
}
|
||||
|
||||
.tag-input {
|
||||
height: 30px;
|
||||
margin-left: 8px;
|
||||
border: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tag-input:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.tag-dropdown {
|
||||
vertical-align: center;
|
||||
margin-right: 8px;
|
||||
display: inline-block;
|
||||
flex-grow: 2;
|
||||
}
|
||||
|
||||
.tag-dropdown-content {
|
||||
display: none;
|
||||
position: absolute;
|
||||
background-color: #f9f9f9;
|
||||
min-width: 160px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
z-index: 15;
|
||||
padding: 4px 0;
|
||||
overflow-y: auto;
|
||||
max-height: 800px;
|
||||
}
|
||||
|
||||
.tag-dropdown-item {
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.tag-dropdown-item:hover {
|
||||
background-color: #dfdfdf;
|
||||
}
|
||||
|
||||
.hlight{
|
||||
background:#dfdfdf;
|
||||
}
|
||||
|
||||
.tag-input:focus + .tag-dropdown-content,
|
||||
.tag-input + .tag-dropdown-content:active {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -230,3 +230,19 @@ ul.nav.nav-pills.flex-column {
|
|||
.thead-highlight {
|
||||
background-color:#485460;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background-color: #444444;
|
||||
}
|
||||
|
||||
.tag-dropdown-content {
|
||||
background-color: #333333;
|
||||
}
|
||||
|
||||
.tag-dropdown-item:hover {
|
||||
background-color: #222222;
|
||||
}
|
||||
|
||||
.hlight{
|
||||
background:#444444;
|
||||
}
|
||||
|
|
|
@ -1040,6 +1040,15 @@ function post(path, params, method='post') {
|
|||
form.submit();
|
||||
}
|
||||
|
||||
function isMobile() {
|
||||
var result = false;
|
||||
// device detection
|
||||
if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|ipad|iris|kindle|Android|Silk|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(navigator.userAgent) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(navigator.userAgent.substring(0, 4))) {
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const font = new FontFaceObserver('Material Icons', {weight: 400});
|
||||
font.load().then(function() {
|
||||
$j('.material-icons').css('display', 'inline-block');
|
||||
|
|
|
@ -207,6 +207,13 @@ if ( $Event->Id() and !file_exists($Event->Path()) )
|
|||
</div>
|
||||
</div>
|
||||
<?php if ( $Event->Id() ) { ?>
|
||||
<div class="tags-container">
|
||||
<div class="tag-dropdown">
|
||||
<!-- input type has to be "search" (not "text") so that the Enter button (not Next) works on mobile Chrome browser. -->
|
||||
<input type="search" id="tagInput" class="tag-input" placeholder="Add tag" data-role="tagsinput">
|
||||
<div class="tag-dropdown-content"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BEGIN VIDEO CONTENT ROW -->
|
||||
<div id="inner-content">
|
||||
<div class="d-flex flex-row">
|
||||
|
|
|
@ -59,7 +59,12 @@ if (!$filter->Id()) {
|
|||
'val' => $num_terms ? '' : (isset($_COOKIE['eventsEndDateTimeEnd']) ? $_COOKIE['eventsEndDateTimeEnd'] : ''),
|
||||
'cnj' => 'and', 'cookie'=>'eventsEndDateTimeEnd'));
|
||||
}
|
||||
$filter->sort_terms(['Group','Monitor','StartDateTime','EndDateTime']);
|
||||
if (!$filter->has_term('Tags')) {
|
||||
$filter->addTerm(array('attr' => 'Tags', 'op' => '=',
|
||||
'val' => $num_terms ? '' : (isset($_COOKIE['eventsTags']) ? $_COOKIE['eventsTags'] : ''),
|
||||
'cnj' => 'and', 'cookie'=>'eventsTags'));
|
||||
}
|
||||
$filter->sort_terms(['Group','Monitor','StartDateTime','EndDateTime','Tags']);
|
||||
#$filter->addTerm(array('cnj'=>'and', 'attr'=>'AlarmFrames', 'op'=> '>', 'val'=>'10'));
|
||||
#$filter->addTerm(array('cnj'=>'and', 'attr'=>'StartDateTime', 'op'=> '<=', 'val'=>''));
|
||||
}
|
||||
|
@ -149,6 +154,7 @@ data-min-width="562"
|
|||
<th data-sortable="true" data-field="Emailed" class="Emailed"><?php echo translate('Emailed') ?></th>
|
||||
<th data-sortable="true" data-field="Monitor" class="Monitor"><?php echo translate('Monitor') ?></th>
|
||||
<th data-sortable="true" data-field="Cause" class="Cause" data-click-to-select="false"><?php echo translate('Cause') ?></th>
|
||||
<th data-sortable="true" data-field="Tags" class="Tags"><?php echo translate('Tags') ?></th>
|
||||
<th data-sortable="true" data-field="StartDateTime" class="StartDateTime"><?php echo translate('AttrStartTime') ?></th>
|
||||
<th data-sortable="true" data-field="EndDateTime" class="EndDateTime"><?php echo translate('AttrEndTime') ?></th>
|
||||
<th data-sortable="true" data-field="Length" class="Length"><?php echo translate('Duration') ?></th>
|
||||
|
|
|
@ -135,6 +135,10 @@ foreach ( dbFetchAll('SELECT Id, Name, MonitorId FROM Zones ORDER BY lower(`Name
|
|||
}
|
||||
}
|
||||
}
|
||||
$availableTags = array();
|
||||
foreach ( dbFetchAll('SELECT Id, Name FROM Tags ORDER BY LastAssignedDate DESC') AS $tag ) {
|
||||
$availableTags[$tag['Id']] = validHtmlStr($tag['Name']);
|
||||
}
|
||||
|
||||
xhtmlHeaders(__FILE__, translate('EventFilter'));
|
||||
echo getBodyTopHTML();
|
||||
|
@ -194,6 +198,7 @@ $sort_fields = array(
|
|||
'Id' => translate('AttrId'),
|
||||
'Name' => translate('AttrName'),
|
||||
'Cause' => translate('AttrCause'),
|
||||
'Tags' => translate('Tags'),
|
||||
'DiskSpace' => translate('AttrDiskSpace'),
|
||||
'Notes' => translate('AttrNotes'),
|
||||
'MonitorName' => translate('AttrMonitorName'),
|
||||
|
|
|
@ -26,9 +26,12 @@ var streamStatus = null;
|
|||
var lastEventId = 0;
|
||||
var zmsBroke = false; //Use alternate navigation if zms has crashed
|
||||
var wasHidden = false;
|
||||
var availableTags = [];
|
||||
var selectedTags = [];
|
||||
|
||||
$j(document).on("keydown", "", function(e) {
|
||||
e = e || window.event;
|
||||
if (!$j(".tag-input").is(":focus")) {
|
||||
if ( $j(".modal").is(":visible") ) {
|
||||
if (e.key === "Enter") {
|
||||
if ( $j("#deleteConfirm").is(":visible") ) {
|
||||
|
@ -59,10 +62,18 @@ $j(document).on("keydown", "", function(e) {
|
|||
} else {
|
||||
pauseClicked();
|
||||
}
|
||||
} else if (e.key === "ArrowDown") {
|
||||
if (e.ctrlKey) {
|
||||
addTag(availableTags[0]);
|
||||
} else {
|
||||
$j("#tagInput").focus();
|
||||
showDropdown();
|
||||
}
|
||||
} else {
|
||||
console.log('Modal is not visible: key not implemented: ', e.key, ' keyCode: ', e.keyCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function streamReq(data) {
|
||||
|
@ -1046,6 +1057,9 @@ function onStatsResize(vidWidth) {
|
|||
}
|
||||
|
||||
function initPage() {
|
||||
getAvailableTags();
|
||||
getSelectedTags();
|
||||
|
||||
// Load the event stats
|
||||
getStat();
|
||||
|
||||
|
@ -1259,6 +1273,85 @@ function initPage() {
|
|||
}
|
||||
});
|
||||
document.addEventListener('fullscreenchange', fullscreenChangeEvent);
|
||||
|
||||
if (isMobile()) { // Mobile
|
||||
// Event listener for adding tags when Space or Comma key is pressed on mobile devices
|
||||
// Mobile Firefox is consistent with Desktop Firefox and Desktop Chrome supporting event.key for space and comma.
|
||||
// Mobile Chrome always returns Unidentified for event.key for space and comma.
|
||||
$j('#tagInput').on('input', function(event) {
|
||||
var key = this.value.substr(-1).charCodeAt(0);
|
||||
if (key === 32 || key === 44) { // Space or Comma
|
||||
const tagInput = $j(this);
|
||||
const tagValue = tagInput.val().slice(0, -1).trim();
|
||||
addOrCreateTag(tagValue);
|
||||
event.preventDefault(); // Prevent the key from being entered in the input field
|
||||
}
|
||||
});
|
||||
// Event listener for adding tags when Enter key is pressed on mobile devices
|
||||
// All mobile and desktop browsers don't pick up on Enter as 'input'.
|
||||
// Mobile Chrome 'input' doesn't pick up "Next" button as Enter.
|
||||
$j('#tagInput').on('keydown', function(event) {
|
||||
var key = event.key;
|
||||
if (key === "Enter") { // Enter
|
||||
const tagInput = $j(this);
|
||||
const tagValue = tagInput.val().trim();
|
||||
addOrCreateTag(tagValue);
|
||||
event.preventDefault(); // Prevent the key from being entered in the input field
|
||||
}
|
||||
});
|
||||
} else { // Desktop
|
||||
// Event listener for adding tags when Enter key is pressed or highlighting available tag when up/down arrows are pressed
|
||||
$j('#tagInput').on('keydown', function(event) {
|
||||
event = event || window.event;
|
||||
var $hlight = $j('div.tag-dropdown-item.hlight');
|
||||
var $div = $j('div.tag-dropdown-item');
|
||||
if (event.key === "ArrowDown") {
|
||||
if (event.ctrlKey) {
|
||||
addTag(availableTags[0]);
|
||||
} else if ($div.is(":visible")) {
|
||||
$hlight.removeClass('hlight').next().addClass('hlight');
|
||||
if ($hlight.next().length == 0) {
|
||||
$div.eq(0).addClass('hlight');
|
||||
}
|
||||
} else {
|
||||
showDropdown();
|
||||
}
|
||||
} else if (event.key === "ArrowUp") {
|
||||
$hlight.removeClass('hlight').prev().addClass('hlight');
|
||||
if ($hlight.prev().length == 0) {
|
||||
$div.eq(-1).addClass('hlight');
|
||||
}
|
||||
} else if (event.key === "Enter") {
|
||||
var tagValue = $hlight.text();
|
||||
if (!tagValue) {
|
||||
const tagInput = $j(this);
|
||||
tagValue = tagInput.val().trim();
|
||||
}
|
||||
addOrCreateTag(tagValue);
|
||||
} else if (event.key === " " || event.key === ",") {
|
||||
const tagInput = $j(this);
|
||||
const tagValue = tagInput.val().trim();
|
||||
addOrCreateTag(tagValue);
|
||||
event.preventDefault(); // Prevent the key from being entered in the input field
|
||||
} else if (event.key === "Escape") {
|
||||
$j("#tagInput").blur();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Event listener for typing in the tag input
|
||||
$j('#tagInput').on('input', showDropdown);
|
||||
|
||||
// Event listener for clicking in the tag input
|
||||
$j('#tagInput').on('focus', showDropdown);
|
||||
|
||||
// Event listener for removing tags
|
||||
$j('.tags-container').on('click', '.tag-remove', function() {
|
||||
const tagElement = $j(this).closest('.tag');
|
||||
const tag = tagElement.data('tag');
|
||||
removeTag(tag);
|
||||
});
|
||||
|
||||
streamPlay();
|
||||
|
||||
if ( parseInt(ZM_OPT_USE_GEOLOCATION) && parseFloat(eventData.Latitude) && parseFloat(eventData.Longitude)) {
|
||||
|
@ -1287,6 +1380,129 @@ function initPage() {
|
|||
} // end if ZM_OPT_USE_GEOLOCATION
|
||||
} // end initPage
|
||||
|
||||
function addOrCreateTag(tagValue) {
|
||||
const tagNames = availableTags.map((t) => t.Name.toLowerCase());
|
||||
const index = tagNames.indexOf(tagValue.toLowerCase());
|
||||
if (index > -1) {
|
||||
addTag(availableTags[index]);
|
||||
$j('.tag-dropdown-content').hide();
|
||||
} else if (tagValue.trim().length > 0) {
|
||||
createTag(tagValue);
|
||||
}
|
||||
}
|
||||
|
||||
function clickTag() {
|
||||
const tagName = $j(this).text();
|
||||
const selectedTag = availableTags.find((tag) => tag.Name === tagName);
|
||||
addTag(selectedTag);
|
||||
}
|
||||
|
||||
function showDropdown() {
|
||||
const dropdownContent = $j('.tag-dropdown-content');
|
||||
dropdownContent.empty();
|
||||
const input = $j('#tagInput').val().trim();
|
||||
|
||||
var matchingTags = [];
|
||||
if (availableTags) {
|
||||
matchingTags = availableTags.filter(function(tag) {
|
||||
var isMatch = tag.Name.toLowerCase().includes(input.toLowerCase());
|
||||
return isMatch && !isDup(tag.Name);
|
||||
});
|
||||
}
|
||||
|
||||
matchingTags.forEach(function(tag) {
|
||||
const dropdownItem = $j('<div>', {class: 'tag-dropdown-item', text: tag.Name});
|
||||
dropdownItem.appendTo(dropdownContent); // Append the element to the dropdown content
|
||||
});
|
||||
|
||||
if (matchingTags.length > 0) {
|
||||
$j('.tag-dropdown-content').off('click');
|
||||
$j('.tag-dropdown-content').on('click', '.tag-dropdown-item', clickTag);
|
||||
$j('.tag-dropdown-content').show();
|
||||
} else {
|
||||
$j('.tag-dropdown-content').hide();
|
||||
}
|
||||
}
|
||||
|
||||
function isDup(tagName) {
|
||||
return $j('.tag-text').filter(function() {
|
||||
var elemText = $j(this).text();
|
||||
return elemText === tagName;
|
||||
}).length != 0;
|
||||
}
|
||||
|
||||
function formatTag(tag) {
|
||||
const tagName = tag.Name;
|
||||
const tagElement = $j('<div>', {class: 'tag'});
|
||||
tagElement.data('tag', tag);
|
||||
tagElement.append($j('<span>', {class: 'tag-text', text: tagName}));
|
||||
tagElement.append($j('<span>', {class: 'tag-remove', text: '\u00D7'}));
|
||||
$j('.tag-dropdown').before(tagElement);
|
||||
}
|
||||
|
||||
function addTag(tag) {
|
||||
if (tag.Name.trim() !== '' && !isDup(tag.Name)) {
|
||||
$j.getJSON(thisUrl + '?request=event&action=addtag&tid=' + tag.Id + '&id=' + eventData.Id)
|
||||
.done(function(data) {
|
||||
formatTag(tag);
|
||||
selectedTags.push(tag);
|
||||
|
||||
// Move the added tag to the front(top) of the availableTags array
|
||||
const index = availableTags.map((t) => t.Id).indexOf(tag.Id);
|
||||
availableTags.splice(0, 0, availableTags.splice(index, 1)[0]);
|
||||
})
|
||||
.fail(logAjaxFail);
|
||||
} else {
|
||||
$j('.tag-dropdown-content').hide();
|
||||
}
|
||||
$j('#tagInput').val('');
|
||||
$j('#tagInput').blur();
|
||||
}
|
||||
|
||||
function removeTag(tag) {
|
||||
$j.getJSON(thisUrl + '?request=event&action=removetag&tid=' + tag.Id + '&id=' + eventData.Id)
|
||||
.done(function(data) {
|
||||
$j('.tag-text').filter(function() {
|
||||
return $j(this).text() === tag.Name;
|
||||
}).parent().remove();
|
||||
if (data.response > 0) {
|
||||
getAvailableTags();
|
||||
}
|
||||
})
|
||||
.fail(logAjaxFail);
|
||||
}
|
||||
|
||||
function createTag(tagName) {
|
||||
$j.getJSON(thisUrl + '?request=tags&action=createtag&tname=' + tagName)
|
||||
.done(function(data) {
|
||||
if (data.response.length > 0) {
|
||||
var tag = data.response[0];
|
||||
if (availableTags) {
|
||||
availableTags.splice(0, 0, tag);
|
||||
}
|
||||
addTag(tag);
|
||||
}
|
||||
})
|
||||
.fail(logAjaxFail);
|
||||
}
|
||||
|
||||
function getAvailableTags() {
|
||||
$j.getJSON(thisUrl + '?request=tags&action=getavailabletags')
|
||||
.done(function(data) {
|
||||
availableTags = data.response;
|
||||
})
|
||||
.fail(logAjaxFail);
|
||||
}
|
||||
|
||||
function getSelectedTags() {
|
||||
$j.getJSON(thisUrl + '?request=event&action=getselectedtags&id=' + eventData.Id)
|
||||
.done(function(data) {
|
||||
selectedTags = data.response;
|
||||
selectedTags.forEach((tag) => formatTag(tag));
|
||||
})
|
||||
.fail(logAjaxFail);
|
||||
}
|
||||
|
||||
var toggleZonesButton = document.getElementById('toggleZonesButton');
|
||||
if (toggleZonesButton) toggleZonesButton.addEventListener('click', toggleZones);
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ var eventData = {
|
|||
MonitorId: '<?php echo $Event->MonitorId() ?>',
|
||||
MonitorName: '<?php echo validJsStr($monitor->Name()) ?>',
|
||||
Cause: '<?php echo validHtmlStr($Event->Cause()) ?>',
|
||||
<!-- Tags: '<?php echo validHtmlStr($Event->Tags()) ?>', -->
|
||||
Notes: `<?php echo $Event->Notes()?>`,
|
||||
Width: '<?php echo $Event->Width() ?>',
|
||||
Height: '<?php echo $Event->Height() ?>',
|
||||
|
@ -57,6 +58,8 @@ var eventDataStrings = {
|
|||
MonitorId: '<?php echo translate('AttrMonitorId') ?>',
|
||||
MonitorName: '<?php echo translate('AttrMonitorName') ?>',
|
||||
Cause: '<?php echo translate('Cause') ?>',
|
||||
<!-- Tags is not necessary since tags are displayed above -->
|
||||
<!-- Tags: '<?php echo translate('Tags') ?>', -->
|
||||
Notes: '<?php echo translate('Notes') ?>',
|
||||
StartDateTimeFormatted: '<?php echo translate('AttrStartTime') ?>',
|
||||
EndDateTimeFormatted: '<?php echo translate('AttrEndTime') ?>',
|
||||
|
|
|
@ -220,7 +220,7 @@ function initPage() {
|
|||
|
||||
// Hide these columns on first run when no cookie is saved
|
||||
if (!getCookie('zmEventsTable.bs.table.columns')) {
|
||||
table.bootstrapTable('hideColumn', 'Archived');
|
||||
// table.bootstrapTable('hideColumn', 'Archived');
|
||||
table.bootstrapTable('hideColumn', 'Emailed');
|
||||
}
|
||||
|
||||
|
@ -420,6 +420,11 @@ function initPage() {
|
|||
}
|
||||
});
|
||||
|
||||
window.onpageshow = function(evt) {
|
||||
console.log('Refreshing table');
|
||||
table.bootstrapTable('refresh');
|
||||
};
|
||||
|
||||
table.bootstrapTable('resetSearch');
|
||||
// The table is initially given a hidden style, so now that we are done rendering, show it
|
||||
table.show();
|
||||
|
@ -429,10 +434,12 @@ function filterEvents() {
|
|||
filterQuery = '';
|
||||
$j('#fieldsTable input').each(function(index) {
|
||||
const el = $j(this);
|
||||
console.log('input index: '+index+' this: '+encodeURIComponent(el.val()));
|
||||
filterQuery += '&'+encodeURIComponent(el.attr('name'))+'='+encodeURIComponent(el.val());
|
||||
});
|
||||
$j('#fieldsTable select').each(function(index) {
|
||||
const el = $j(this);
|
||||
console.log('select index: '+index+' this: '+encodeURIComponent(el.val()));
|
||||
filterQuery += '&'+encodeURIComponent(el.attr('name'))+'='+encodeURIComponent(el.val());
|
||||
});
|
||||
console.log(filterQuery);
|
||||
|
|
|
@ -291,6 +291,13 @@ function parseRows(rows) {
|
|||
});
|
||||
var monitorVal = inputTds.eq(4).children().val();
|
||||
inputTds.eq(4).html(monitorSelect).children().val(monitorVal).chosen({width: '101%'});
|
||||
} else if ( attr == 'Tags' ) { // Tags
|
||||
var tagSelect = $j('<select></select>').attr('name', queryPrefix + rowNum + '][val]').attr('id', queryPrefix + rowNum + '][val]');
|
||||
for (var key in availableTags) {
|
||||
tagSelect.append('<option value="' + key + '">' + escapeHTML(availableTags[key]) + '</option>');
|
||||
};
|
||||
var tagVal = inputTds.eq(4).children().val();
|
||||
inputTds.eq(4).html(tagSelect).children().val(tagVal).chosen({width: '101%'});
|
||||
} else if ( attr == 'ExistsInFileSystem' ) {
|
||||
var select = $j('<select></select>').attr('name', queryPrefix + rowNum + '][val]').attr('id', queryPrefix + rowNum + '][val]');
|
||||
for ( var booleanVal in booleanValues ) {
|
||||
|
@ -400,6 +407,14 @@ function manageModalBtns(id) {
|
|||
}
|
||||
}
|
||||
|
||||
// function getAvailableTags() {
|
||||
// $j.getJSON(thisUrl + '?request=tags&action=getavailabletags')
|
||||
// .done(function(data) {
|
||||
// return data.response;
|
||||
// })
|
||||
// .fail(logAjaxFail);
|
||||
// }
|
||||
|
||||
function initPage() {
|
||||
updateButtons($j('#executeButton')[0]);
|
||||
$j('#Id').chosen();
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
global $servers;
|
||||
global $storageareas;
|
||||
global $monitors;
|
||||
global $availableTags;
|
||||
global $zones;
|
||||
global $booleanValues;
|
||||
global $filter;
|
||||
|
@ -27,6 +28,7 @@ const states = <?php echo isset($states) ? json_encode($states) : '{}' ?>;
|
|||
const servers = <?php echo isset($servers) ? json_encode($servers) : '{}' ?>;
|
||||
const storageareas = <?php echo isset($storageareas) ? json_encode($storageareas) : '{}' ?>;
|
||||
const monitors = <?php echo isset($monitors) ? json_encode($monitors) : '{}' ?>;
|
||||
const availableTags = <?php echo isset($availableTags) ? json_encode($availableTags) : '[]' ?>;
|
||||
const sorted_monitor_ids = <?php echo isset($monitors) ? json_encode(array_keys($monitors)) : '[]' ?>;
|
||||
const zones = <?php echo isset($zones) ? json_encode($zones) : '{}' ?>;
|
||||
const booleanValues = <?php echo json_encode($booleanValues) ?>;
|
||||
|
|
|
@ -130,9 +130,63 @@ $chart = array(
|
|||
$monitors = array();
|
||||
|
||||
# The as E, and joining with Monitors is required for the filterSQL filters.
|
||||
$rangeSql = 'SELECT min(E.StartDateTime) AS MinTime, max(E.EndDateTime) AS MaxTime FROM Events AS E INNER JOIN Monitors AS M ON (E.MonitorId = M.Id) WHERE NOT isnull(E.StartDateTime) AND NOT isnull(E.EndDateTime)';
|
||||
$eventsSql = 'SELECT E.* FROM Events AS E INNER JOIN Monitors AS M ON (E.MonitorId = M.Id) WHERE NOT isnull(StartDateTime)';
|
||||
$eventIdsSql = 'SELECT E.Id FROM Events AS E INNER JOIN Monitors AS M ON (E.MonitorId = M.Id) WHERE NOT isnull(StartDateTime)';
|
||||
$rangeSql = '
|
||||
SELECT
|
||||
min(E.StartDateTime)
|
||||
AS MinTime,
|
||||
max(E.EndDateTime)
|
||||
AS MaxTime,
|
||||
GROUP_CONCAT(T.Name SEPARATOR ", ")
|
||||
AS Tags
|
||||
FROM Events
|
||||
AS E
|
||||
INNER JOIN Monitors
|
||||
AS M
|
||||
ON (E.MonitorId = M.Id)
|
||||
LEFT JOIN Events_Tags
|
||||
AS ET
|
||||
ON E.Id = ET.EventId
|
||||
LEFT JOIN Tags
|
||||
AS T
|
||||
ON T.Id = ET.TagId
|
||||
WHERE NOT isnull(E.StartDateTime)
|
||||
AND NOT isnull(E.EndDateTime)';
|
||||
|
||||
$eventsSql = '
|
||||
SELECT
|
||||
E.*,
|
||||
GROUP_CONCAT(T.Name SEPARATOR ", ")
|
||||
AS Tags
|
||||
FROM Events
|
||||
AS E
|
||||
INNER JOIN Monitors
|
||||
AS M
|
||||
ON (E.MonitorId = M.Id)
|
||||
LEFT JOIN Events_Tags
|
||||
AS ET
|
||||
ON E.Id = ET.EventId
|
||||
LEFT JOIN Tags
|
||||
AS T
|
||||
ON T.Id = ET.TagId
|
||||
WHERE NOT isnull(StartDateTime)';
|
||||
|
||||
$eventIdsSql = '
|
||||
SELECT
|
||||
E.Id,
|
||||
GROUP_CONCAT(T.Name SEPARATOR ", ")
|
||||
AS TagsFROM Events
|
||||
AS E
|
||||
INNER JOIN Monitors
|
||||
AS M
|
||||
ON (E.MonitorId = M.Id)
|
||||
LEFT JOIN Events_Tags
|
||||
AS ET
|
||||
ON E.Id = ET.EventId
|
||||
LEFT JOIN Tags
|
||||
AS T
|
||||
ON T.Id = ET.TagId
|
||||
WHERE NOT isnull(StartDateTime)';
|
||||
|
||||
$eventsValues = array();
|
||||
|
||||
if ( count($user->unviewableMonitorIds()) ) {
|
||||
|
|
|
@ -392,6 +392,7 @@ if ( canView('Events') && ($monitor->Type() != 'WebSite') ) {
|
|||
<th data-sortable="false" data-field="Id"><?php echo translate('Id') ?></th>
|
||||
<th data-sortable="false" data-field="Name"><?php echo translate('Name') ?></th>
|
||||
<th data-sortable="false" data-field="Cause"><?php echo translate('Cause') ?></th>
|
||||
<th data-sortable="false" data-field="Tags"><?php echo translate('Tags') ?></th>
|
||||
<th data-sortable="false" data-field="Notes"><?php echo translate('Notes') ?></th>
|
||||
<th data-sortable="false" data-field="StartDateTime"><?php echo translate('AttrStartTime') ?></th>
|
||||
<th data-sortable="false" data-field="EndDateTime"><?php echo translate('AttrEndTime') ?></th>
|
||||
|
|
Loading…
Reference in New Issue