mirror of
https://github.com/ZoneMinder/zoneminder.git
synced 2026-10-03 07:55:22 -04:00
feat: add customizable navbar/sidebar menu via Options > Menu tab
Add Menu_Items table to store per-item enabled state, custom labels, and sort order. Admins can enable/disable menu items, set custom labels, and reorder via drag-and-drop in Options > Menu tab. Refactor hardcoded get*HTML() calls in buildSidebarMenu(), getNormalNavBarHTML(), and getCollapsedNavBarHTML() to data-driven renderMenuItems() that reads from DB with fallback for empty table. Remove deprecated Cycle view from menu (functionality merged into Watch view). Add reset button to restore default menu configuration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
2d3e0df07d
commit
4bd56c22d9
8 files changed
+428
-135
No files matched your search
@@ -1348,6 +1348,37 @@ CREATE TABLE `Notifications` (
|
||||
CONSTRAINT `Notifications_ibfk_1` FOREIGN KEY (`UserId`) REFERENCES `Users` (`Id`) ON DELETE CASCADE
|
||||
) ENGINE=@ZM_MYSQL_ENGINE@;
|
||||
|
||||
--
|
||||
-- Table structure for table `Menu_Items`
|
||||
--
|
||||
|
||||
DROP TABLE IF EXISTS `Menu_Items`;
|
||||
CREATE TABLE `Menu_Items` (
|
||||
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`MenuKey` varchar(32) NOT NULL,
|
||||
`Enabled` tinyint(1) NOT NULL DEFAULT 1,
|
||||
`Label` varchar(64) DEFAULT NULL,
|
||||
`SortOrder` smallint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`Id`),
|
||||
UNIQUE KEY `Menu_Items_MenuKey_idx` (`MenuKey`)
|
||||
) ENGINE=@ZM_MYSQL_ENGINE@;
|
||||
|
||||
INSERT INTO `Menu_Items` (`MenuKey`, `Enabled`, `SortOrder`) VALUES
|
||||
('Console', 1, 10),
|
||||
('Montage', 1, 20),
|
||||
('MontageReview', 1, 30),
|
||||
('Events', 1, 40),
|
||||
('Options', 1, 50),
|
||||
('Log', 1, 60),
|
||||
('Devices', 1, 70),
|
||||
('IntelGpu', 1, 80),
|
||||
('Groups', 1, 90),
|
||||
('Filters', 1, 100),
|
||||
('Snapshots', 1, 110),
|
||||
('Reports', 1, 120),
|
||||
('ReportEventAudit', 1, 130),
|
||||
('Map', 1, 140);
|
||||
|
||||
source @PKGDATADIR@/db/Object_Types.sql
|
||||
-- We generally don't alter triggers, we drop and re-create them, so let's keep them in a separate file that we can just source in update scripts.
|
||||
source @PKGDATADIR@/db/triggers.sql
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
--
|
||||
-- Add Menu_Items table for customizable navbar/sidebar menu
|
||||
--
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = DATABASE()
|
||||
AND table_name = 'Menu_Items'
|
||||
) > 0,
|
||||
"SELECT 'Table Menu_Items already exists'",
|
||||
"CREATE TABLE `Menu_Items` (
|
||||
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
||||
`MenuKey` varchar(32) NOT NULL,
|
||||
`Enabled` tinyint(1) NOT NULL DEFAULT 1,
|
||||
`Label` varchar(64) DEFAULT NULL,
|
||||
`SortOrder` smallint NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (`Id`),
|
||||
UNIQUE KEY `Menu_Items_MenuKey_idx` (`MenuKey`)
|
||||
) ENGINE=InnoDB"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
--
|
||||
-- Seed default menu items if table is empty
|
||||
--
|
||||
|
||||
SET @s = (SELECT IF(
|
||||
(SELECT COUNT(*) FROM `Menu_Items`) > 0,
|
||||
"SELECT 'Menu_Items already has data'",
|
||||
"INSERT INTO `Menu_Items` (`MenuKey`, `Enabled`, `SortOrder`) VALUES
|
||||
('Console', 1, 10),
|
||||
('Montage', 1, 20),
|
||||
('MontageReview', 1, 30),
|
||||
('Events', 1, 40),
|
||||
('Options', 1, 50),
|
||||
('Log', 1, 60),
|
||||
('Devices', 1, 70),
|
||||
('IntelGpu', 1, 80),
|
||||
('Groups', 1, 90),
|
||||
('Filters', 1, 100),
|
||||
('Snapshots', 1, 110),
|
||||
('Reports', 1, 120),
|
||||
('ReportEventAudit', 1, 130),
|
||||
('Map', 1, 140)"
|
||||
));
|
||||
|
||||
PREPARE stmt FROM @s;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace ZM;
|
||||
require_once('database.php');
|
||||
require_once('Object.php');
|
||||
|
||||
class MenuItem extends ZM_Object {
|
||||
protected static $table = 'Menu_Items';
|
||||
|
||||
protected $defaults = array(
|
||||
'Id' => null,
|
||||
'MenuKey' => '',
|
||||
'Enabled' => 1,
|
||||
'Label' => null,
|
||||
'SortOrder' => 0,
|
||||
);
|
||||
|
||||
public static function find($parameters = array(), $options = array()) {
|
||||
return ZM_Object::_find(self::class, $parameters, $options);
|
||||
}
|
||||
|
||||
public static function find_one($parameters = array(), $options = array()) {
|
||||
return ZM_Object::_find_one(self::class, $parameters, $options);
|
||||
}
|
||||
|
||||
public function displayLabel() {
|
||||
if ($this->{'Label'} !== null && $this->{'Label'} !== '') {
|
||||
return $this->{'Label'};
|
||||
}
|
||||
return translate($this->{'MenuKey'});
|
||||
}
|
||||
}
|
||||
@@ -189,5 +189,47 @@ if ( $action == 'delete' ) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ($action == 'menuitems') {
|
||||
if (!canEdit('System')) {
|
||||
ZM\Warning('Need System permission to edit menu items');
|
||||
} else if (isset($_REQUEST['items'])) {
|
||||
require_once('includes/MenuItem.php');
|
||||
$allItems = ZM\MenuItem::find();
|
||||
foreach ($allItems as $item) {
|
||||
$id = $item->Id();
|
||||
$enabled = isset($_REQUEST['items'][$id]['Enabled']) ? 1 : 0;
|
||||
$label = isset($_REQUEST['items'][$id]['Label']) ? trim($_REQUEST['items'][$id]['Label']) : null;
|
||||
$sortOrder = isset($_REQUEST['items'][$id]['SortOrder']) ? intval($_REQUEST['items'][$id]['SortOrder']) : $item->SortOrder();
|
||||
if ($label === '') $label = null;
|
||||
$item->save([
|
||||
'Enabled' => $enabled,
|
||||
'Label' => $label,
|
||||
'SortOrder' => $sortOrder,
|
||||
]);
|
||||
}
|
||||
}
|
||||
$redirect = '?view=options&tab=menu';
|
||||
} else if ($action == 'resetmenu') {
|
||||
if (!canEdit('System')) {
|
||||
ZM\Warning('Need System permission to reset menu items');
|
||||
} else {
|
||||
dbQuery('DELETE FROM Menu_Items');
|
||||
dbQuery("INSERT INTO `Menu_Items` (`MenuKey`, `Enabled`, `SortOrder`) VALUES
|
||||
('Console', 1, 10),
|
||||
('Montage', 1, 20),
|
||||
('MontageReview', 1, 30),
|
||||
('Events', 1, 40),
|
||||
('Options', 1, 50),
|
||||
('Log', 1, 60),
|
||||
('Devices', 1, 70),
|
||||
('IntelGpu', 1, 80),
|
||||
('Groups', 1, 90),
|
||||
('Filters', 1, 100),
|
||||
('Snapshots', 1, 110),
|
||||
('Reports', 1, 120),
|
||||
('ReportEventAudit', 1, 130),
|
||||
('Map', 1, 140)");
|
||||
}
|
||||
$redirect = '?view=options&tab=menu';
|
||||
} // end if object vs action
|
||||
?>
|
||||
@@ -179,7 +179,7 @@ function getBodyTopHTML() {
|
||||
}
|
||||
} // end function getBodyTopHTML
|
||||
|
||||
function buildMenuItem($viewItemName, $id, $itemName, $href, $icon, $classNameForTag_A = '', $subMenu = '') {
|
||||
function buildMenuItem($viewItemName, $id, $itemName, $href, $icon, $classNameForTag_A = '', $subMenu = '', $skipTranslate = false) {
|
||||
global $view;
|
||||
/* Highlighting the active menu section */
|
||||
if ($viewItemName == 'watch') {
|
||||
@@ -187,40 +187,96 @@ function buildMenuItem($viewItemName, $id, $itemName, $href, $icon, $classNameFo
|
||||
} else {
|
||||
$activeClass = $view == $viewItemName ? ' active' : '';
|
||||
}
|
||||
$itemName = translate($itemName);
|
||||
if (!$skipTranslate) $itemName = translate($itemName);
|
||||
$result = '
|
||||
<li id="' . $id . '" class="menu-item '.$activeClass.'">
|
||||
<a href="' . $href . '" class="' . $classNameForTag_A . '">
|
||||
<span class="menu-icon"><i class="material-icons">' . $icon . '</i></span>
|
||||
<span class="menu-title">'.$itemName.'</span>
|
||||
<span class="menu-title">'.htmlspecialchars($itemName).'</span>
|
||||
</a>
|
||||
</li>'.PHP_EOL;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function getMenuItemFunctions() {
|
||||
return [
|
||||
'Console' => 'getConsoleHTML',
|
||||
'Montage' => 'getMontageHTML',
|
||||
'MontageReview' => 'getMontageReviewHTML',
|
||||
'Events' => 'getEventsHTML',
|
||||
'Options' => 'getOptionsHTML',
|
||||
'Log' => 'getLogHTML',
|
||||
'Devices' => 'getDevicesHTML',
|
||||
'IntelGpu' => 'getIntelGpuHTML',
|
||||
'Groups' => 'getGroupsHTML',
|
||||
'Filters' => 'getFilterHTML',
|
||||
'Snapshots' => 'getSnapshotsHTML',
|
||||
'Reports' => 'getReportsHTML',
|
||||
'ReportEventAudit' => 'getRprtEvntAuditHTML',
|
||||
'Map' => 'getMapHTML',
|
||||
];
|
||||
}
|
||||
|
||||
function getMenuItems() {
|
||||
static $cached = null;
|
||||
if ($cached === null) {
|
||||
require_once('includes/MenuItem.php');
|
||||
$cached = ZM\MenuItem::find([], ['order' => 'SortOrder ASC']);
|
||||
}
|
||||
return $cached;
|
||||
}
|
||||
|
||||
// Functions that take only ($forLeftBar, $customLabel) - no $view parameter
|
||||
function getMenuFuncsNoView() {
|
||||
return ['getConsoleHTML', 'getOptionsHTML', 'getLogHTML', 'getDevicesHTML', 'getIntelGpuHTML'];
|
||||
}
|
||||
|
||||
function renderMenuItems($forLeftBar = false) {
|
||||
global $view;
|
||||
$menuItems = getMenuItems();
|
||||
$funcMap = getMenuItemFunctions();
|
||||
$result = '';
|
||||
|
||||
if (empty($menuItems)) {
|
||||
// Fallback: no DB rows yet, render all in default order
|
||||
foreach ($funcMap as $key => $funcName) {
|
||||
$noViewFuncs = getMenuFuncsNoView();
|
||||
if (in_array($funcName, $noViewFuncs)) {
|
||||
$result .= $funcName($forLeftBar);
|
||||
} else {
|
||||
$result .= $funcName($view, $forLeftBar);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$noViewFuncs = getMenuFuncsNoView();
|
||||
foreach ($menuItems as $item) {
|
||||
if (!$item->Enabled()) continue;
|
||||
$key = $item->MenuKey();
|
||||
if (!isset($funcMap[$key])) continue;
|
||||
|
||||
$funcName = $funcMap[$key];
|
||||
$customLabel = ($item->Label() !== null && $item->Label() !== '') ? $item->displayLabel() : null;
|
||||
|
||||
if (in_array($funcName, $noViewFuncs)) {
|
||||
$result .= $funcName($forLeftBar, $customLabel);
|
||||
} else {
|
||||
$result .= $funcName($view, $forLeftBar, $customLabel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$result .= getAdditionalLinksHTML($view, $forLeftBar);
|
||||
return $result;
|
||||
}
|
||||
|
||||
function buildSidebarMenu() {
|
||||
global $view;
|
||||
global $user;
|
||||
if ( $user and $user->Username() ) {
|
||||
$menuForAuthUser = '
|
||||
<li class="menu-header"><span> GENERAL </span></li> ' .
|
||||
getConsoleHTML($forLeftBar = true) .
|
||||
getMontageHTML($view, $forLeftBar = true) .
|
||||
getCycleHTML($view, $forLeftBar = true) .
|
||||
getMontageReviewHTML($view, $forLeftBar = true) .
|
||||
getEventsHTML($view, $forLeftBar = true) .
|
||||
getOptionsHTML($forLeftBar = true) .
|
||||
getLogHTML($forLeftBar = true) .
|
||||
getDevicesHTML($forLeftBar = true) .
|
||||
getIntelGpuHTML($forLeftBar = true) .
|
||||
getGroupsHTML($view, $forLeftBar = true) .
|
||||
getFilterHTML($view, $forLeftBar = true) .
|
||||
getSnapshotsHTML($view, $forLeftBar = true) .
|
||||
getReportsHTML($view, $forLeftBar = true) .
|
||||
getRprtEvntAuditHTML($view, $forLeftBar = true) .
|
||||
getMapHTML($view, $forLeftBar = true) .
|
||||
getAdditionalLinksHTML($view, $forLeftBar = true)
|
||||
renderMenuItems($forLeftBar = true)
|
||||
;
|
||||
} else { // USER IS NOT AUTHORIZED!
|
||||
$menuForAuthUser = '';
|
||||
@@ -511,21 +567,7 @@ function getNormalNavBarHTML($running, $user, $bandwidth_options, $view, $skin)
|
||||
|
||||
// *** Build the navigation bar menu items ***
|
||||
echo '<ul class="nav navbar-nav align-self-start justify-content-center">';
|
||||
echo getConsoleHTML();
|
||||
echo getOptionsHTML();
|
||||
echo getLogHTML();
|
||||
echo getDevicesHTML();
|
||||
echo getIntelGpuHTML();
|
||||
echo getGroupsHTML($view);
|
||||
echo getFilterHTML($view);
|
||||
echo getCycleHTML($view);
|
||||
echo getMontageHTML($view);
|
||||
echo getMontageReviewHTML($view);
|
||||
echo getSnapshotsHTML($view);
|
||||
echo getReportsHTML($view);
|
||||
echo getRprtEvntAuditHTML($view);
|
||||
echo getMapHTML($view);
|
||||
echo getAdditionalLinksHTML($view);
|
||||
echo renderMenuItems($forLeftBar = false);
|
||||
echo getHeaderFlipHTML();
|
||||
echo '</ul></div><div id="accountstatus">';
|
||||
echo '<ul class="nav navbar-nav justify-content-end align-self-start flex-grow-1">';
|
||||
@@ -669,21 +711,7 @@ function getCollapsedNavBarHTML($running, $user, $bandwidth_options, $view, $ski
|
||||
<?php
|
||||
if ( $user and $user->Username() ) {
|
||||
echo '<ul class="navbar-nav">';
|
||||
echo getConsoleHTML();
|
||||
echo getOptionsHTML();
|
||||
echo getLogHTML();
|
||||
echo getDevicesHTML();
|
||||
echo getIntelGpuHTML();
|
||||
echo getGroupsHTML($view);
|
||||
echo getFilterHTML($view);
|
||||
echo getCycleHTML($view);
|
||||
echo getMontageHTML($view);
|
||||
echo getMontageReviewHTML($view);
|
||||
echo getSnapshotsHTML($view);
|
||||
echo getReportsHTML($view);
|
||||
echo getRprtEvntAuditHTML($view);
|
||||
echo getMapHTML($view);
|
||||
echo getAdditionalLinksHTML($view);
|
||||
echo renderMenuItems($forLeftBar = false);
|
||||
echo '</ul>';
|
||||
}
|
||||
?>
|
||||
@@ -963,23 +991,26 @@ function getNavBrandHTML() {
|
||||
}
|
||||
|
||||
// Returns the html representing the Console menu item
|
||||
function getConsoleHTML($forLeftBar = false) {
|
||||
function getConsoleHTML($forLeftBar = false, $customLabel = null) {
|
||||
global $user;
|
||||
$result = '';
|
||||
$label = $customLabel !== null ? $customLabel : 'Console';
|
||||
$skipTranslate = $customLabel !== null;
|
||||
|
||||
if (count($user->viewableMonitorIds()) or !ZM\Monitor::find_one()) {
|
||||
if ($forLeftBar) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'console',
|
||||
$id = 'getConsoleHTML',
|
||||
$itemName = 'Console',
|
||||
$itemName = $label,
|
||||
$href = '?view=console',
|
||||
$icon = 'dashboard',
|
||||
$classNameForTag_A = '',
|
||||
$subMenu = ''
|
||||
$subMenu = '',
|
||||
$skipTranslate
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getConsoleHTML" class="nav-item"><a class="nav-link" href="?view=console">'.translate('Console').'</a></li>'.PHP_EOL;
|
||||
$result .= '<li id="getConsoleHTML" class="nav-item"><a class="nav-link" href="?view=console">'.htmlspecialchars($skipTranslate ? $label : translate($label)).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -987,9 +1018,10 @@ function getConsoleHTML($forLeftBar = false) {
|
||||
}
|
||||
|
||||
// Returns the html representing the Options menu item
|
||||
function getOptionsHTML($forLeftBar = false) {
|
||||
function getOptionsHTML($forLeftBar = false, $customLabel = null) {
|
||||
global $zmMenu;
|
||||
$result = '';
|
||||
$label = $customLabel !== null ? $customLabel : translate('Options');
|
||||
|
||||
// Sorting order of the "Options" submenu items. If a submenu item is in the DB but is not here, it will be automatically added to the end of the list.
|
||||
$categoryDisplayOrder = [
|
||||
@@ -1026,7 +1058,8 @@ function getOptionsHTML($forLeftBar = false) {
|
||||
'privacy',
|
||||
'MQTT',
|
||||
'telemetry',
|
||||
'version'
|
||||
'version',
|
||||
'menu'
|
||||
]);
|
||||
$zmMenu::buildSubMenuOptions($categoryDisplayOrder);
|
||||
|
||||
@@ -1059,12 +1092,12 @@ function getOptionsHTML($forLeftBar = false) {
|
||||
<li id="getOptionsHTML" class="menu-item sub-menu '.($view == "options" ? ' open' : '').'">
|
||||
<a href="#<!--?view='.$view_.'&tab=system-->">
|
||||
<span class="menu-icon"><i class="material-icons">settings</i></span>
|
||||
<span class="menu-title">'.translate('Options').'</span>
|
||||
<span class="menu-title">'.htmlspecialchars($label).'</span>
|
||||
</a>
|
||||
' . $subMenuOptions . '
|
||||
</li>'.PHP_EOL;
|
||||
} else {
|
||||
$result .= '<li id="getOptionsHTML" class="nav-item"><a class="nav-link" href="?view=options">'.translate('Options').'</a></li>'.PHP_EOL;
|
||||
$result .= '<li id="getOptionsHTML" class="nav-item"><a class="nav-link" href="?view=options">'.htmlspecialchars($label).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1072,9 +1105,11 @@ function getOptionsHTML($forLeftBar = false) {
|
||||
}
|
||||
|
||||
// Returns the html representing the Log menu item
|
||||
function getLogHTML($forLeftBar = false) {
|
||||
function getLogHTML($forLeftBar = false, $customLabel = null) {
|
||||
$result = '';
|
||||
|
||||
$label = $customLabel !== null ? $customLabel : 'Log';
|
||||
$skipTranslate = $customLabel !== null;
|
||||
|
||||
if ( canView('System') ) {
|
||||
if ( ZM\logToDatabase() > ZM\Logger::NOLOG ) {
|
||||
$logstate = logState();
|
||||
@@ -1083,18 +1118,19 @@ function getLogHTML($forLeftBar = false) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'log',
|
||||
$id = 'getLogHTML',
|
||||
$itemName = 'Log',
|
||||
$itemName = $label,
|
||||
$href = '?view=log',
|
||||
$icon = 'notification_important',
|
||||
$classNameForTag_A = $class,
|
||||
$subMenu = ''
|
||||
$subMenu = '',
|
||||
$skipTranslate
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getLogHTML" class="nav-item"><a class="nav-link '.$class.'" href="?view=log">'.translate('Log').'</a></li>'.PHP_EOL;
|
||||
$result .= '<li id="getLogHTML" class="nav-item"><a class="nav-link '.$class.'" href="?view=log">'.htmlspecialchars($skipTranslate ? $label : translate($label)).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
@@ -1116,22 +1152,25 @@ function getLogIconHTML() {
|
||||
}
|
||||
|
||||
// Returns the html representing the X10 Devices menu item
|
||||
function getDevicesHTML($forLeftBar = false) {
|
||||
function getDevicesHTML($forLeftBar = false, $customLabel = null) {
|
||||
$result = '';
|
||||
$label = $customLabel !== null ? $customLabel : 'Devices';
|
||||
$skipTranslate = $customLabel !== null;
|
||||
|
||||
if ( ZM_OPT_X10 && canView('Devices') ) {
|
||||
if ($forLeftBar) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'devices',
|
||||
$id = 'getDevicesHTML',
|
||||
$itemName = 'Devices',
|
||||
$itemName = $label,
|
||||
$href = '?view=devices',
|
||||
$icon = 'devices_other',
|
||||
$classNameForTag_A = '',
|
||||
$subMenu = ''
|
||||
$subMenu = '',
|
||||
$skipTranslate
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getDevicesHTML" class="nav-item"><a class="nav-link" href="?view=devices">'.translate('Devices').'</a></li>'.PHP_EOL;
|
||||
$result .= '<li id="getDevicesHTML" class="nav-item"><a class="nav-link" href="?view=devices">'.htmlspecialchars($skipTranslate ? $label : translate($label)).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1139,8 +1178,10 @@ function getDevicesHTML($forLeftBar = false) {
|
||||
}
|
||||
|
||||
// Returns the html representing the Intel GPU status menu item
|
||||
function getIntelGpuHTML($forLeftBar = false) {
|
||||
function getIntelGpuHTML($forLeftBar = false, $customLabel = null) {
|
||||
$result = '';
|
||||
$label = $customLabel !== null ? $customLabel : 'Intel GPU';
|
||||
$skipTranslate = $customLabel !== null;
|
||||
|
||||
// Only show if intel_gpu_top is available and user can view System
|
||||
if (canView('System')) {
|
||||
@@ -1151,14 +1192,15 @@ function getIntelGpuHTML($forLeftBar = false) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'intelgpu',
|
||||
$id = 'getIntelGpuHTML',
|
||||
$itemName = 'Intel GPU',
|
||||
$itemName = $label,
|
||||
$href = '?view=intelgpu',
|
||||
$icon = 'memory',
|
||||
$classNameForTag_A = '',
|
||||
$subMenu = ''
|
||||
$subMenu = '',
|
||||
$skipTranslate
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getIntelGpuHTML" class="nav-item"><a class="nav-link" href="?view=intelgpu">Intel GPU</a></li>'.PHP_EOL;
|
||||
$result .= '<li id="getIntelGpuHTML" class="nav-item"><a class="nav-link" href="?view=intelgpu">'.htmlspecialchars($label).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1167,80 +1209,63 @@ function getIntelGpuHTML($forLeftBar = false) {
|
||||
}
|
||||
|
||||
// Returns the html representing the Groups menu item
|
||||
function getGroupsHTML($view, $forLeftBar = false) {
|
||||
function getGroupsHTML($view, $forLeftBar = false, $customLabel = null) {
|
||||
$result = '';
|
||||
if ( !canView('Groups') ) return $result;
|
||||
$label = $customLabel !== null ? $customLabel : 'Groups';
|
||||
$skipTranslate = $customLabel !== null;
|
||||
|
||||
$class = $view == 'groups' ? ' selected' : '';
|
||||
if ($forLeftBar) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'groups',
|
||||
$id = 'getGroupsHTML',
|
||||
$itemName = 'Groups',
|
||||
$itemName = $label,
|
||||
$href = '?view=groups',
|
||||
$icon = 'group',
|
||||
$classNameForTag_A = '',
|
||||
$subMenu = ''
|
||||
$subMenu = '',
|
||||
$skipTranslate
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getGroupsHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=groups">'. translate('Groups') .'</a></li>'.PHP_EOL;
|
||||
$result .= '<li id="getGroupsHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=groups">'.htmlspecialchars($skipTranslate ? $label : translate($label)).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Returns the html representing the Filter menu item
|
||||
function getFilterHTML($view, $forLeftBar = false) {
|
||||
function getFilterHTML($view, $forLeftBar = false, $customLabel = null) {
|
||||
$result = '';
|
||||
if ( !canView('Events') ) return $result;
|
||||
|
||||
$label = $customLabel !== null ? $customLabel : 'Filters';
|
||||
$skipTranslate = $customLabel !== null;
|
||||
|
||||
$class = $view == 'filter' ? ' selected' : '';
|
||||
if ($forLeftBar) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'filter',
|
||||
$id = 'getFilterHTML',
|
||||
$itemName = 'Filters',
|
||||
$itemName = $label,
|
||||
$href = '?view=filter',
|
||||
$icon = 'filter_alt',
|
||||
$classNameForTag_A = '',
|
||||
$subMenu = ''
|
||||
$subMenu = '',
|
||||
$skipTranslate
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getFilterHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=filter">'.translate('Filters').'</a></li>'.PHP_EOL;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Returns the html representing the Cycle menu item
|
||||
function getCycleHTML($view, $forLeftBar = false) {
|
||||
$result = '';
|
||||
|
||||
if ( canView('Stream') ) {
|
||||
$class = $view == 'cycle' ? ' selected' : '';
|
||||
if ($forLeftBar) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'watch',
|
||||
$id = 'getCycleHTML',
|
||||
$itemName = 'Cycle',
|
||||
$href = '?view=watch&cycle=true',
|
||||
//$icon = 'cyclone',
|
||||
$icon = 'repeat',
|
||||
$classNameForTag_A = '',
|
||||
$subMenu = ''
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getCycleHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=watch&cycle=true">' .translate('Cycle'). '</a></li>'.PHP_EOL;
|
||||
}
|
||||
$result .= '<li id="getFilterHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=filter">'.htmlspecialchars($skipTranslate ? $label : translate($label)).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Returns the html representing the Montage menu item
|
||||
function getMontageHTML($view, $forLeftBar = false) {
|
||||
function getMontageHTML($view, $forLeftBar = false, $customLabel = null) {
|
||||
global $user;
|
||||
$result = '';
|
||||
$label = $customLabel !== null ? $customLabel : 'Montage';
|
||||
$skipTranslate = $customLabel !== null;
|
||||
|
||||
if (canView('Stream') and count($user->viewableMonitorIds())) {
|
||||
$class = $view == 'montage' ? ' selected' : '';
|
||||
@@ -1248,14 +1273,15 @@ function getMontageHTML($view, $forLeftBar = false) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'montage',
|
||||
$id = 'getMontageHTML',
|
||||
$itemName = 'Montage',
|
||||
$itemName = $label,
|
||||
$href = '?view=montage',
|
||||
$icon = 'live_tv',
|
||||
$classNameForTag_A = '',
|
||||
$subMenu = ''
|
||||
$subMenu = '',
|
||||
$skipTranslate
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getMontageHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=montage">' .translate('Montage'). '</a></li>'.PHP_EOL;
|
||||
$result .= '<li id="getMontageHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=montage">'.htmlspecialchars($skipTranslate ? $label : translate($label)).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1263,9 +1289,11 @@ function getMontageHTML($view, $forLeftBar = false) {
|
||||
}
|
||||
|
||||
// Returns the html representing the MontageReview menu item
|
||||
function getMontageReviewHTML($view, $forLeftBar = false) {
|
||||
function getMontageReviewHTML($view, $forLeftBar = false, $customLabel = null) {
|
||||
$result = '';
|
||||
|
||||
$label = $customLabel !== null ? $customLabel : 'MontageReview';
|
||||
$skipTranslate = $customLabel !== null;
|
||||
|
||||
if ( canView('Events') ) {
|
||||
if ( isset($_REQUEST['filter']['Query']['terms']['attr']) ) {
|
||||
$terms = $_REQUEST['filter']['Query']['terms'];
|
||||
@@ -1287,14 +1315,15 @@ function getMontageReviewHTML($view, $forLeftBar = false) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'montagereview',
|
||||
$id = 'getMontageReviewHTML',
|
||||
$itemName = 'MontageReview',
|
||||
$itemName = $label,
|
||||
$href = '?view=montagereview' .$live,
|
||||
$icon = 'movie',
|
||||
$classNameForTag_A = '',
|
||||
$subMenu = ''
|
||||
$subMenu = '',
|
||||
$skipTranslate
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getMontageReviewHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=montagereview' .$live. '">'.translate('MontageReview').'</a></li>'.PHP_EOL;
|
||||
$result .= '<li id="getMontageReviewHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=montagereview' .$live. '">'.htmlspecialchars($skipTranslate ? $label : translate($label)).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1302,8 +1331,10 @@ function getMontageReviewHTML($view, $forLeftBar = false) {
|
||||
}
|
||||
|
||||
// Returns the html representing the Montage menu item
|
||||
function getSnapshotsHTML($view, $forLeftBar = false) {
|
||||
function getSnapshotsHTML($view, $forLeftBar = false, $customLabel = null) {
|
||||
$result = '';
|
||||
$label = $customLabel !== null ? $customLabel : 'Snapshots';
|
||||
$skipTranslate = $customLabel !== null;
|
||||
|
||||
if (defined('ZM_FEATURES_SNAPSHOTS') and ZM_FEATURES_SNAPSHOTS and canView('Snapshots')) {
|
||||
$class = $view == 'snapshots' ? ' selected' : '';
|
||||
@@ -1311,14 +1342,15 @@ function getSnapshotsHTML($view, $forLeftBar = false) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'snapshots',
|
||||
$id = 'getSnapshotsHTML',
|
||||
$itemName = 'Snapshots',
|
||||
$itemName = $label,
|
||||
$href = '?view=snapshots',
|
||||
$icon = 'preview',
|
||||
$classNameForTag_A = '',
|
||||
$subMenu = ''
|
||||
$subMenu = '',
|
||||
$skipTranslate
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getSnapshotsHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=snapshots">' .translate('Snapshots'). '</a></li>'.PHP_EOL;
|
||||
$result .= '<li id="getSnapshotsHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=snapshots">'.htmlspecialchars($skipTranslate ? $label : translate($label)).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1326,9 +1358,11 @@ function getSnapshotsHTML($view, $forLeftBar = false) {
|
||||
}
|
||||
|
||||
// Returns the html representing the Events menu item
|
||||
function getEventsHTML($view, $forLeftBar = false) {
|
||||
function getEventsHTML($view, $forLeftBar = false, $customLabel = null) {
|
||||
global $user;
|
||||
$result = '';
|
||||
$label = $customLabel !== null ? $customLabel : 'Events';
|
||||
$skipTranslate = $customLabel !== null;
|
||||
|
||||
if (canView('Events')) {
|
||||
$class = $view == 'events' ? ' selected' : '';
|
||||
@@ -1336,22 +1370,25 @@ function getEventsHTML($view, $forLeftBar = false) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'events',
|
||||
$id = 'getEventsHTML',
|
||||
$itemName = 'Events',
|
||||
$itemName = $label,
|
||||
$href = '?view=events',
|
||||
$icon = 'event',
|
||||
$classNameForTag_A = '',
|
||||
$subMenu = ''
|
||||
$subMenu = '',
|
||||
$skipTranslate
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getEventsHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=events">' .translate('Events'). '</a></li>'.PHP_EOL;
|
||||
$result .= '<li id="getEventsHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=events">'.htmlspecialchars($skipTranslate ? $label : translate($label)).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function getReportsHTML($view, $forLeftBar = false) {
|
||||
function getReportsHTML($view, $forLeftBar = false, $customLabel = null) {
|
||||
$result = '';
|
||||
$label = $customLabel !== null ? $customLabel : 'Reports';
|
||||
$skipTranslate = $customLabel !== null;
|
||||
|
||||
if (canView('Events')) {
|
||||
$class = ($view == 'reports' or $view == 'report') ? ' selected' : '';
|
||||
@@ -1359,14 +1396,15 @@ function getReportsHTML($view, $forLeftBar = false) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'reports',
|
||||
$id = 'getReportsHTML',
|
||||
$itemName = 'Reports',
|
||||
$itemName = $label,
|
||||
$href = '?view=reports',
|
||||
$icon = 'report',
|
||||
$classNameForTag_A = '',
|
||||
$subMenu = ''
|
||||
$subMenu = '',
|
||||
$skipTranslate
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getReportsHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=reports">'.translate('Reports').'</a></li>'.PHP_EOL;
|
||||
$result .= '<li id="getReportsHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=reports">'.htmlspecialchars($skipTranslate ? $label : translate($label)).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1374,8 +1412,10 @@ function getReportsHTML($view, $forLeftBar = false) {
|
||||
}
|
||||
|
||||
// Returns the html representing the Audit Events Report menu item
|
||||
function getRprtEvntAuditHTML($view, $forLeftBar = false) {
|
||||
function getRprtEvntAuditHTML($view, $forLeftBar = false, $customLabel = null) {
|
||||
$result = '';
|
||||
$label = $customLabel !== null ? $customLabel : 'ReportEventAudit';
|
||||
$skipTranslate = $customLabel !== null;
|
||||
|
||||
if ( canView('Events') ) {
|
||||
$class = $view == 'report_event_audit' ? ' selected' : '';
|
||||
@@ -1383,14 +1423,15 @@ function getRprtEvntAuditHTML($view, $forLeftBar = false) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'report_event_audit',
|
||||
$id = 'getRprtEvntAuditHTML',
|
||||
$itemName = 'ReportEventAudit',
|
||||
$itemName = $label,
|
||||
$href = '?view=report_event_audit',
|
||||
$icon = 'shield',
|
||||
$classNameForTag_A = '',
|
||||
$subMenu = ''
|
||||
$subMenu = '',
|
||||
$skipTranslate
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getRprtEvntAuditHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=report_event_audit">'.translate('ReportEventAudit').'</a></li>'.PHP_EOL;
|
||||
$result .= '<li id="getRprtEvntAuditHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=report_event_audit">'.htmlspecialchars($skipTranslate ? $label : translate($label)).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1398,8 +1439,10 @@ function getRprtEvntAuditHTML($view, $forLeftBar = false) {
|
||||
}
|
||||
|
||||
// Returns the html representing the Audit Events Report menu item
|
||||
function getMapHTML($view, $forLeftBar = false) {
|
||||
function getMapHTML($view, $forLeftBar = false, $customLabel = null) {
|
||||
$result = '';
|
||||
$label = $customLabel !== null ? $customLabel : 'Map';
|
||||
$skipTranslate = $customLabel !== null;
|
||||
|
||||
if (defined('ZM_OPT_USE_GEOLOCATION') and ZM_OPT_USE_GEOLOCATION) {
|
||||
$class = $view == 'map' ? ' selected' : '';
|
||||
@@ -1407,14 +1450,15 @@ function getMapHTML($view, $forLeftBar = false) {
|
||||
$result .= buildMenuItem(
|
||||
$viewItemName = 'map',
|
||||
$id = 'getMapHTML',
|
||||
$itemName = 'Map',
|
||||
$itemName = $label,
|
||||
$href = '?view=map',
|
||||
$icon = 'language',
|
||||
$classNameForTag_A = '',
|
||||
$subMenu = ''
|
||||
$subMenu = '',
|
||||
$skipTranslate
|
||||
);
|
||||
} else {
|
||||
$result .= '<li id="getMapHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=map">'.translate('Map').'</a></li>'.PHP_EOL;
|
||||
$result .= '<li id="getMapHTML" class="nav-item"><a class="nav-link'.$class.'" href="?view=map">'.htmlspecialchars($skipTranslate ? $label : translate($label)).'</a></li>'.PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
require_once('includes/MenuItem.php');
|
||||
$menuItems = ZM\MenuItem::find([], ['order' => 'SortOrder ASC']);
|
||||
$canEdit = canEdit('System');
|
||||
?>
|
||||
<form name="menuItemsForm" method="post" action="?">
|
||||
<input type="hidden" name="view" value="options"/>
|
||||
<input type="hidden" name="tab" value="menu"/>
|
||||
<input type="hidden" name="action" value="menuitems"/>
|
||||
<div id="options">
|
||||
<div class="row pb-2">
|
||||
<div class="col">
|
||||
<div id="contentButtons">
|
||||
<?php if ($canEdit) { ?>
|
||||
<button type="submit" class="btn btn-primary"><?php echo translate('Save') ?></button>
|
||||
<button type="button" id="sortMenuBtn" data-on-click-this="sortMenuItems">
|
||||
<i class="material-icons" title="<?php echo translate('Click and drag rows to change order') ?>">swap_vert</i>
|
||||
<span class="text"><?php echo translate('Sort') ?></span>
|
||||
</button>
|
||||
<button type="submit" name="action" value="resetmenu" class="btn btn-warning"
|
||||
onclick="return confirm('<?php echo addslashes(translate('Reset menu items to defaults?')) ?>');"
|
||||
><?php echo translate('Reset') ?></button>
|
||||
<?php } ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper-scroll-table">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<table class="table table-striped" id="menuItemsTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="text-left"><?php echo translate('Enabled') ?></th>
|
||||
<th class="text-left"><?php echo translate('Name') ?></th>
|
||||
<th class="text-left"><?php echo translate('Custom Label') ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="menuItemsBody">
|
||||
<?php foreach ($menuItems as $item) { ?>
|
||||
<tr id="menuItem-<?php echo $item->Id() ?>">
|
||||
<td>
|
||||
<input type="hidden" name="items[<?php echo $item->Id() ?>][Id]" value="<?php echo $item->Id() ?>"/>
|
||||
<input type="hidden" name="items[<?php echo $item->Id() ?>][SortOrder]" class="sortOrderInput" value="<?php echo $item->SortOrder() ?>"/>
|
||||
<input type="checkbox" name="items[<?php echo $item->Id() ?>][Enabled]" value="1"
|
||||
<?php echo $item->Enabled() ? 'checked' : '' ?>
|
||||
<?php echo !$canEdit ? 'disabled' : '' ?>
|
||||
/>
|
||||
</td>
|
||||
<td class="text-left"><?php echo htmlspecialchars(translate($item->MenuKey())) ?></td>
|
||||
<td>
|
||||
<input type="text" name="items[<?php echo $item->Id() ?>][Label]"
|
||||
value="<?php echo htmlspecialchars($item->Label() ?? '') ?>"
|
||||
placeholder="<?php echo htmlspecialchars(translate($item->MenuKey())) ?>"
|
||||
<?php echo !$canEdit ? 'disabled' : '' ?>
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@@ -54,6 +54,19 @@ function AddNewRole(el) {
|
||||
window.location.assign(url);
|
||||
}
|
||||
|
||||
function sortMenuItems(button) {
|
||||
if (button.classList.contains('btn-success')) {
|
||||
$j('#menuItemsBody').sortable('disable');
|
||||
// Update hidden sort order fields based on new row positions
|
||||
$j('#menuItemsBody tr').each(function(index) {
|
||||
$j(this).find('.sortOrderInput').val((index + 1) * 10);
|
||||
});
|
||||
} else {
|
||||
$j('#menuItemsBody').sortable('enable');
|
||||
}
|
||||
button.classList.toggle('btn-success');
|
||||
}
|
||||
|
||||
function initPage() {
|
||||
const NewStorageBtn = $j('#NewStorageBtn');
|
||||
const NewServerBtn = $j('#NewServerBtn');
|
||||
@@ -65,6 +78,20 @@ function initPage() {
|
||||
NewServerBtn.prop('disabled', !canEdit.System);
|
||||
|
||||
$j('.bootstraptable').bootstrapTable({icons: icons}).show();
|
||||
|
||||
// Menu items tab: sortable drag-and-drop
|
||||
if ($j('#menuItemsBody').length) {
|
||||
$j('#menuItemsBody').sortable({
|
||||
disabled: true,
|
||||
axis: 'y',
|
||||
cursor: 'move',
|
||||
update: function() {
|
||||
$j('#menuItemsBody tr').each(function(index) {
|
||||
$j(this).find('.sortOrderInput').val((index + 1) * 10);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$j(document).ready(function() {
|
||||
|
||||
@@ -141,7 +141,9 @@ foreach (array_map('basename', glob('skins/'.$skin.'/css/*', GLOB_ONLYDIR)) as $
|
||||
include('_options_roles.php');
|
||||
} else if ($tab == 'API') {
|
||||
include('_options_api.php');
|
||||
} // $tab == API
|
||||
} else if ($tab == 'menu') {
|
||||
include('_options_menu.php');
|
||||
} // $tab == API/menu
|
||||
else {
|
||||
$config = array();
|
||||
$configCats = array();
|
||||
|
||||
Reference in new issue
Block a user