$value) { if (!safe_empty($value)) { switch ($var) { case 'group': case 'group_id': $values = get_group_entities($value); $where_array[$var] = generate_query_values_ng($values, 'device_id'); break; case 'device': case 'device_id': $where_array[$var] = generate_query_values_ng($value, 'device_id'); break; case 'hostname': case 'sysname': case 'sysContact': case 'sysDescr': case 'serial': case 'purpose': $condition = str_contains_array($value, ['*', '?']) ? 'LIKE' : '%LIKE%'; $where_array[$var] = generate_query_values_ng($value, $var, $condition); break; case 'location_text': $condition = str_contains_array($value, ['*', '?']) ? 'LIKE' : '%LIKE%'; $where_array[$var] = generate_query_values_ng($value, 'devices.location', $condition); break; case 'location': $where_array[$var] = generate_query_values_ng($value, 'devices.location'); break; case 'location_lat': case 'location_lon': case 'location_country': case 'location_state': case 'location_county': case 'location_city': if ($GLOBALS['config']['geocoding']['enable']) { $where_array[$var] = generate_query_values_ng($value, 'devices_locations.' . $var); } break; case 'os': case 'version': case 'hardware': case 'vendor': case 'features': case 'type': case 'status': case 'status_type': case 'distro': case 'ignore': case 'disabled': case 'snmpable': case 'snmp_community': case 'snmp_context': case 'snmp_transport': case 'snmp_port': case 'snmp_maxrep': case 'snmp_version': $where_array[$var] = generate_query_values_ng($value, $var); break; case 'graph': $where_array[$var] = generate_query_values_ng(devices_with_graph($value), "devices.device_id"); } } } return $where_array; } function devices_with_graph($graph) { $devices = []; $sql = "SELECT `device_id` FROM `device_graphs` WHERE `graph` = ? AND `enabled` = '1'"; foreach (dbFetchRows($sql, [$graph]) as $entry) { $devices[$entry['device_id']] = $entry['device_id']; } return $devices; } function build_devices_sort($vars) { $order = ''; switch ($vars['sort']) { case 'uptime': case 'location': case 'version': case 'features': case 'type': case 'os': case 'sysName': case 'device_id': $order = ' ORDER BY `devices`.`' . $vars['sort'] . '`'; if ($vars['sort_order'] == "desc") { $order .= " DESC"; } break; case 'domain': // Special order hostnames in Domain Order // SELECT `hostname`, // SUBSTRING_INDEX(SUBSTRING_INDEX(`hostname`,'.',-3),'.',1) AS `leftmost`, // SUBSTRING_INDEX(SUBSTRING_INDEX(`hostname`,'.',-2),'.',1) AS `middle`, // SUBSTRING_INDEX(`hostname`,'.',-1) AS `rightmost` // FROM `devices` ORDER by `middle`, `rightmost`, `leftmost`; if ($vars['sort_order'] == "desc") { $order = ' ORDER BY `middle` DESC, `rightmost` DESC, `leftmost` DESC'; } else { $order = ' ORDER BY `middle`, `rightmost`, `leftmost`'; } break; case 'hostname': default: $order = ' ORDER BY `devices`.`hostname`'; if ($vars['sort_order'] == "desc") { $order .= " DESC"; } break; } return $order; } // DOCME needs phpdoc block function print_device_header($device, $args = []) { global $config; if (!is_array($device)) { print_error("Invalid device passed to print_device_header()!"); } $div_class = 'box box-solid'; if (!safe_empty($args['div-class'])) { $div_class .= " " . $args['div-class']; } echo '
'; $location_url = escape_html($device['location']); if (device_permitted($device)) { $location_url = '' . $location_url . ''; } echo ' '; if (device_permitted($device) && !$args['no_graphs']) { echo ''; } // Only show graphs for device_permitted() echo('
' . get_device_icon($device) . ' ' . generate_device_link($device) . '
' . $location_url . '
'; // Only show graphs for device_permitted(), don't show device graphs to users who can only see a single entity. if (isset($config['os'][$device['os']]['graphs'])) { $graphs = $config['os'][$device['os']]['graphs']; } elseif (isset($device['os_group'], $config['os'][$device['os_group']]['graphs'])) { $graphs = $config['os'][$device['os_group']]['graphs']; } else { // Default group $graphs = $config['os_group']['default']['graphs']; } $graph_array = []; //$graph_array['height'] = "100"; //$graph_array['width'] = "310"; $graph_array['to'] = get_time(); $graph_array['device'] = $device['device_id']; $graph_array['type'] = "device_bits"; $graph_array['from'] = get_time('day'); $graph_array['legend'] = "no"; $graph_array['height'] = "45"; $graph_array['width'] = "150"; $graph_array['style'] = ['width: 150px !important']; // Fix for FF issue on HiDPI screen $graph_array['bg'] = "FFFFFF00"; // Preprocess device graphs array $graphs_enabled = []; foreach ($device['graphs'] as $graph) { $graphs_enabled[] = $graph['graph']; } foreach ($graphs as $entry) { if ($entry && in_array(str_replace('device_', '', $entry), $graphs_enabled, TRUE)) { $graph_array['type'] = $entry; if (preg_match(OBS_PATTERN_GRAPH_TYPE, $entry, $graphtype)) { $type = $graphtype['type']; $subtype = $graphtype['subtype']; $text = $config['graph_types'][$type][$subtype]['descr']; } else { $text = nicecase($entry); // Fallback to the type itself as a string, should not happen! } echo '
'; //echo generate_graph_tag($graph_array); echo generate_graph_popup($graph_array); echo '
' . $text . '
'; echo '
'; } } echo '
'); } function print_device_row($device, $vars = ['view' => 'basic'], $link_vars = []) { global $config, $cache; if (!is_array($device)) { print_error("Invalid device passed to print_device_row()!"); } if (!is_array($vars)) { $vars = ['view' => $vars]; } // For compatibility humanize_device($device); $tags = [ 'html_row_class' => $device['html_row_class'], 'device_id' => $device['device_id'], 'device_link' => generate_device_link($device, NULL, $link_vars), 'device_url' => generate_device_url($device, $link_vars), 'hardware' => escape_html($device['hardware']), 'features' => escape_html($device['features']), 'os_text' => $device['os_text'], 'version' => escape_html($device['version']), //'sysName' => escape_html($device['sysName']), 'device_uptime' => device_uptime($device, 'short'), 'location' => escape_html(truncate($device['location'], 40, '')) ]; switch (strtolower($config['web_device_name'])) { case 'sysname': case 'purpose': case 'descr': case 'description': $tags['sysName'] = escape_html($device['hostname']); if (!safe_empty($device['sysName'])) { $tags['sysName'] .= ' / ' . escape_html($device['sysName']); } break; default: $tags['sysName'] = escape_html($device['sysName']); } switch ($vars['view']) { case 'detail': case 'details': $table_cols = 7; $tags['device_image'] = get_device_icon($device); $tags['ports_count'] = dbFetchCell("SELECT COUNT(*) FROM `ports` WHERE `device_id` = ? AND `deleted` = ?", [$device['device_id'], 0]); //$tags['sensors_count'] = dbFetchCell("SELECT COUNT(*) FROM `sensors` WHERE `device_id` = ? AND `sensor_deleted` = ?", array($device['device_id'], 0)); //$tags['sensors_count'] += dbFetchCell("SELECT COUNT(*) FROM `status` WHERE `device_id` = ? AND `status_deleted` = ?", array($device['device_id'], 0)); $tags['sensors_count'] = $cache['sensors']['devices'][$device['device_id']]['count']; $tags['sensors_count'] += $cache['statuses']['devices'][$device['device_id']]['count']; $hostbox = ' ' . $tags['device_image'] . ' ' . $tags['device_link'] . '
' . $tags['location'] . ' '; if ($tags['ports_count']) { $hostbox .= ' ' . $tags['ports_count'] . ''; } $hostbox .= '
'; if ($tags['sensors_count']) { $hostbox .= ' '; $sensor_items = []; // Ok if ($event_count = $cache['sensors']['devices'][$device['device_id']]['ok'] + $cache['statuses']['devices'][$device['device_id']]['ok']) { $sensor_items[] = ['event' => 'success', 'text' => $event_count]; } // Warning if ($event_count = $cache['sensors']['devices'][$device['device_id']]['warning'] + $cache['statuses']['devices'][$device['device_id']]['warning']) { $sensor_items[] = ['event' => 'warning', 'text' => $event_count]; } // Alert if ($event_count = $cache['sensors']['devices'][$device['device_id']]['alert'] + $cache['statuses']['devices'][$device['device_id']]['alert']) { $sensor_items[] = ['event' => 'danger', 'text' => $event_count]; } // Ignored if ($event_count = $cache['sensors']['devices'][$device['device_id']]['ignored'] + $cache['statuses']['devices'][$device['device_id']]['ignored']) { $sensor_items[] = ['event' => 'default', 'text' => $event_count]; } $hostbox .= get_label_group($sensor_items); //''.$tags['sensors_count'].''; } $hostbox .= ' ' . $tags['os_text'] . ' ' . $tags['version'] . (!empty($tags['features']) ? ' (' . $tags['features'] . ')' : '') . '
' . $tags['hardware'] . ' ' . $tags['device_uptime'] . '
' . $tags['sysName'] . ' '; break; case 'perf': if ($_SESSION['userlevel'] >= 7) { $tags['device_image'] = get_device_icon($device); $graph_array = [ 'type' => 'device_poller_perf', 'device' => $device['device_id'], 'operation' => 'poll', 'legend' => 'no', 'width' => 600, 'height' => 90, 'from' => $config['time']['week'], 'to' => $config['time']['now'], ]; $hostbox = ' ' . $tags['device_image'] . ' ' . $tags['device_link'] . '
' . $tags['location'] . '
' . generate_graph_tag($graph_array) . '
'; } break; case 'status': $tags['device_image'] = get_device_icon($device); // Graphs $graph_array = []; $graph_array['height'] = "100"; $graph_array['width'] = "310"; $graph_array['to'] = $config['time']['now']; $graph_array['device'] = $device['device_id']; $graph_array['type'] = "device_bits"; $graph_array['from'] = $config['time']['day']; $graph_array['legend'] = "no"; $graph_array['height'] = "45"; $graph_array['width'] = "175"; $graph_array['bg'] = "FFFFFF00"; if (isset($config['os'][$device['os']]['graphs'])) { $graphs = $config['os'][$device['os']]['graphs']; } elseif (isset($device['os_group']) && isset($config['os'][$device['os_group']]['graphs'])) { $graphs = $config['os'][$device['os_group']]['graphs']; } else { // Default group $graphs = $config['os_group']['default']['graphs']; } // Preprocess device graphs array $graphs_enabled = []; foreach ($device['graphs'] as $graph) { $graphs_enabled[] = $graph['graph']; } foreach ($graphs as $entry) { [, $graph_subtype] = explode("_", $entry, 2); if ($entry && in_array(str_replace("device_", "", $entry), $graphs_enabled)) { $graph_array['type'] = $entry; if (isset($config['graph_types']['device'][$graph_subtype])) { $title = $config['graph_types']['device'][$graph_subtype]['descr']; } else { $title = nicecase(str_replace("_", " ", $graph_subtype)); } $tags['graphs'][] = '
' . generate_graph_popup($graph_array) . '
' . $title . '
'; } } $hostbox = ' ' . $tags['device_image'] . ' ' . $tags['device_link'] . '
' . $tags['location'] . ' '; if ($tags['graphs']) { $hostbox .= '' . implode($tags['graphs']) . ''; } $hostbox .= ' '; break; default: // basic $table_cols = 6; $tags['device_image'] = get_device_icon($device); $tags['ports_count'] = dbFetchCell("SELECT COUNT(*) FROM `ports` WHERE `device_id` = ? AND `deleted` = 0;", [$device['device_id']]); $tags['sensors_count'] = dbFetchCell("SELECT COUNT(*) FROM `sensors` WHERE `device_id` = ?;", [$device['device_id']]); $tags['sensors_count'] += dbFetchCell("SELECT COUNT(*) FROM `status` WHERE `device_id` = ?;", [$device['device_id']]); $hostbox = ' ' . $tags['device_image'] . ' ' . $tags['device_link'] . '
' . $tags['location'] . ' ' . $tags['hardware'] . ' ' . $tags['features'] . ' ' . $tags['os_text'] . ' ' . $tags['version'] . ' ' . $tags['device_uptime'] . ' '; } // If we're showing graphs, generate the graph if ($vars['graph']) { $hostbox .= ''; $graph_array['to'] = $config['time']['now']; $graph_array['device'] = $device['device_id']; $graph_array['type'] = 'device_' . $vars['graph']; $hostbox .= generate_graph_row($graph_array); $hostbox .= ''; } echo($hostbox); } /** * Returns icon tag (by default) or icon name for current device array * * @param array $device Array with device info (from DB) * @param bool $base_icon Return complete img tag with icon (by default) or just base icon name * @param bool $dark Prefer dark variant of icon (also set by session var) * * @return string Img tag with icon or base icon name */ function get_device_icon($device, $base_icon = FALSE, $dark = FALSE) { global $config; $icon = 'generic'; if (device_permitted($device)) { $device['os'] = strtolower($device['os']); $model = $config['os'][$device['os']]['model']; if (!safe_empty($device['icon']) && is_file($config['html_dir'] . '/images/os/' . $device['icon'] . '.png')) { // Custom device icon from DB $icon = $device['icon']; } elseif ($model && isset($config['model'][$model][$device['sysObjectID']]['icon']) && is_file($config['html_dir'] . '/images/os/' . $config['model'][$model][$device['sysObjectID']]['icon'] . '.png')) { // Per model icon $icon = $config['model'][$model][$device['sysObjectID']]['icon']; } elseif (isset($config['os'][$device['os']]['icon']) && is_file($config['html_dir'] . '/images/os/' . $config['os'][$device['os']]['icon'] . '.png')) { // Icon defined in os definition $icon = $config['os'][$device['os']]['icon']; } else { if ($device['distro']) { // Icon by distro name // Red Hat Enterprise -> redhat $distro = strtolower(trim(str_replace([ ' Enterprise', 'Red Hat' ], [ '', 'redhat' ], $device['distro']))); $distro = safename($distro); if (is_file($config['html_dir'] . '/images/os/' . $distro . '.png')) { $icon = $distro; } } if ($icon === 'generic' && is_file($config['html_dir'] . '/images/os/' . $device['os'] . '.png')) { // Icon by OS name $icon = $device['os']; } } // Icon by vendor name if ($icon === 'generic' && ($config['os'][$device['os']]['vendor'] || $device['vendor'])) { if ($device['vendor']) { $vendor = $device['vendor']; } else { $vendor = rewrite_vendor($config['os'][$device['os']]['vendor']); // Compatibility, if device not polled for long time } $vendor_safe = safename(strtolower($vendor)); if (isset($config['vendors'][$vendor_safe]['icon'])) { $icon = $config['vendors'][$vendor_safe]['icon']; } elseif (is_file($config['html_dir'] . '/images/os/' . $vendor_safe . '.png')) { $icon = $vendor_safe; } elseif (isset($config['os'][$device['os']]['icons'])) { // Fallback to os alternative icon $icon = array_values($config['os'][$device['os']]['icons'])[0]; } } } // Set dark mode by session if (isset($_SESSION['theme'])) { $dark = str_contains($_SESSION['theme'], 'dark'); } // Prefer dark variant of icon in dark mode if ($dark && is_file($config['html_dir'] . '/images/os/' . $icon . '-dark.png')) { $icon .= '-dark'; } if ($base_icon) { // return base name for os icon return $icon; } // return image html tag $base_url = rtrim($config['base_url'], '/'); $srcset = ''; // Now we always have 2x icon variant! //if (is_file($config['html_dir'] . '/images/os/' . $icon . '_2x.png')) // HiDPI image exist? //{ // Detect allowed screen ratio for current browser $ua_info = detect_browser(); if ($ua_info['screen_ratio'] > 1) { $srcset = ' srcset="' . $base_url . '/images/os/' . $icon . '_2x.png' . ' 2x"'; } //} // Image tag -- FIXME re-engineer this code to do this properly. This is messy. return ''; } // TESTME needs unit testing // DOCME needs phpdoc block function generate_device_url($device, $vars = []) { return generate_url(['page' => 'device', 'device' => $device['device_id']], $vars); } // TESTME needs unit testing // DOCME needs phpdoc block function generate_device_popup_header($device, $vars = []) { humanize_device($device); $device_name = device_name($device); if ($device['hostname'] !== $device_name) { $sysName = $device['hostname']; if (!safe_empty($device['sysName'])) { $sysName .= ' / ' . $device['sysName']; } } else { $sysName = $device['sysName']; } $string = generate_box_open() . ' ' . PHP_EOL; if (device_permitted($device)) { $string .= ' ' . PHP_EOL; } else { $string .= ''; } $string .= '
' . get_device_icon($device) . '' . escape_html(device_name($device)) . '
' . escape_html(truncate($device['location'], 64, '')) . '
' . $device['os_text'] . ' ' . escape_html($device['version']) . '
' . ($device['vendor'] ? escape_html($device['vendor']) . ' ' : '') . escape_html($device['hardware']) . '
' . device_uptime($device, 'short') . '
' . escape_html($sysName) . '
' . generate_device_link($device) . '
' . escape_html(truncate($device['location'], 64, '')) . '
' . generate_box_close(); return $string; } // TESTME needs unit testing // DOCME needs phpdoc block function generate_device_popup($device, $vars = []) { global $config; $content = generate_device_popup_header($device, $vars); if (isset($config['os'][$device['os']]['graphs'])) { $graphs = $config['os'][$device['os']]['graphs']; } elseif (isset($device['os_group'], $config['os'][$device['os_group']]['graphs'])) { $graphs = $config['os'][$device['os_group']]['graphs']; } else { // Default group $graphs = $config['os_group']['default']['graphs']; } // Preprocess device graphs array $graphs_enabled = []; foreach ($device['graphs'] as $graph) { if ($graph['enabled'] != '0') { $graphs_enabled[] = $graph['graph']; } } $count = 0; foreach ($graphs as $entry) { if ($count == 3) { break; } if ($entry && in_array(str_replace('device_', '', $entry), $graphs_enabled, TRUE)) { // No text provided for the minigraph, fetch from array if (preg_match(OBS_PATTERN_GRAPH_TYPE, $entry, $graphtype)) { $type = $graphtype['type']; $subtype = $graphtype['subtype']; $text = $config['graph_types'][$type][$subtype]['descr']; } else { $text = nicecase($entry); // Fallback to the type itself as a string, should not happen! } // FIXME -- function! $graph_array = []; $graph_array['height'] = "100"; $graph_array['width'] = "290"; $graph_array['to'] = get_time(); $graph_array['device'] = $device['device_id']; $graph_array['type'] = $entry; $graph_array['from'] = get_time('day'); $graph_array['legend'] = "no"; $content .= '
'; $content .= "

" . $text . "

"; $content .= generate_graph_tag($graph_array); $graph_array['from'] = get_time('week'); $content .= generate_graph_tag($graph_array); $content .= '
'; $count++; } } //r($content); return $content; } // TESTME needs unit testing // DOCME needs phpdoc block function generate_device_link($device, $text = NULL, $vars = [], $escape = TRUE, $short = FALSE) { if (is_array($device) && !($device['hostname'] && isset($device['status']))) { // partial device array, get full $device = device_by_id_cache($device['device_id']); } elseif (is_numeric($device)) { $device = device_by_id_cache($device); } if (!$device) { return escape_html($text); } if (!device_permitted($device['device_id'])) { $text = device_name($device, $short); return $escape ? escape_html($text) : $text; } $class = device_link_class($device); if (safe_empty($text)) { $text = device_name($device, $short); } $url = generate_device_url($device, $vars); if ($escape) { $text = escape_html($text); } return '' . $text . ''; } // Simple wrapper to generate_device_link() for common usage with only device_name function generate_device_link_short($device, $vars = [], $short = TRUE) { // defaults - always short device name, escaped return generate_device_link($device, NULL, $vars, TRUE, $short); } /** * Generate device form values based on the given form filter, column, and options. * generate_form_values('device') * * The $form_filter can be an array of device IDs to filter the form values. If false * or not an array, no filtering will be applied. * * @param bool|array $form_filter Array of device IDs to filter by, or false for no filtering. * @param string $column The column to use in the device form values (default: 'device_id'). * @param array $options Options to customize the device form values. * Available: filter_mode (include|exclude), show_disabled (bool), show_down (bool), show_icon (bool), subtext (string) * * @return array The generated device form values. */ function generate_device_form_values($form_filter = FALSE, $column = 'device_id', $options = []) { global $cache; $form_items = []; foreach ($cache['devices']['hostname'] as $hostname => $device_id) { // Filter items based on filter_mode if ($options['filter_mode'] === 'include') { if (is_array($form_filter) && !in_array($device_id, $form_filter)) { continue; } } elseif ($options['filter_mode'] === 'exclude') { if (is_array($form_filter) && in_array($device_id, $form_filter)) { continue; } } if (in_array($device_id, $cache['devices']['disabled'])) { if (isset($options['show_disabled'])) { // Force display disabled devices if (!$options['show_disabled']) { continue; } } elseif (!$GLOBALS['config']['web_show_disabled'] && in_array($device_id, $cache['devices']['disabled'])) { continue; } $form_items[$device_id]['group'] = 'DISABLED'; $form_items[$device_id]['class'] = 'bg-disabled'; } elseif (in_array($device_id, $cache['devices']['down'])) { if (isset($options['show_down']) && !$options['show_down']) { continue; } // Skip down $form_items[$device_id]['group'] = 'DOWN'; $form_items[$device_id]['class'] = 'bg-danger'; } else { if (isset($options['up']) && in_array($device_id, $cache['devices']['up'])) { continue; } // Skip up $form_items[$device_id]['group'] = 'UP'; $form_items[$device_id]['class'] = 'bg-info'; } $form_items[$device_id]['name'] = $hostname; if (isset($options['subtext'])) { $device = device_by_id_cache($device_id); $form_items[$device_id]['subtext'] = array_tag_replace($device, $options['subtext']); } if (isset($options['show_icon']) && $options['show_icon']) { $device = $device ?? device_by_id_cache($device_id); $form_items[$device_id]['icon'] = $GLOBALS['config']['devicetypes'][$device['type']]['icon'] ?? $GLOBALS['config']['entities']['device']['icon']; } } return $form_items; } function print_device_permission_box($mode, $perms, $params = []) { global $config; echo generate_box_open([ 'header-border' => TRUE, 'title' => 'Device Permissions' ]); $perms_devices = !safe_empty($perms['device']); if ($perms_devices) { echo('' . PHP_EOL); foreach ($perms['device'] as $device_id => $status) { $device = device_by_id_cache($device_id); echo(''); } echo('
' . get_icon($config['devicetypes'][$device['type']]['icon'] ?? $config['entities']['device']['icon']) . generate_device_link($device) . ' ' . $device['location'] . ' '); $form = []; $form['type'] = 'simple'; $action_del = $mode === 'role' ? 'role_entity_del' : 'user_perm_del'; // Elements $form['row'][0]['auth_secret'] = [ 'type' => 'hidden', 'value' => $_SESSION['auth_secret'] ]; $form['row'][0]['entity_id'] = [ 'type' => 'hidden', 'value' => $device['device_id'] ]; $form['row'][0]['entity_type'] = [ 'type' => 'hidden', 'value' => 'device' ]; $form['row'][0]['action'] = [ 'type' => 'hidden', 'value' => $action_del ]; $form['row'][0]['submit'] = [ 'type' => 'submit', 'name' => ' ', 'class' => 'btn-danger btn-mini', 'icon' => 'icon-trash', 'value' => $action_del ]; print_form($form); unset($form); echo('
' . PHP_EOL); } else { echo('

This '.$mode.' currently has no permitted devices

'); } // Devices $permissions_list = array_keys((array)$perms['device']); // Display devices this user doesn't have Permissions to $form = []; $form['type'] = 'simple'; $form['style'] = 'padding: 7px; margin: 0px;'; // Elements $form['row'][0]['auth_secret'] = [ 'type' => 'hidden', 'value' => $_SESSION['auth_secret'] ]; if ($mode === 'role') { $action_add = 'role_entity_add'; $form['row'][0]['role_id'] = [ 'type' => 'hidden', 'value' => $params['role_id'] ]; } else { $action_add = 'user_perm_add'; $form['row'][0]['user_id'] = [ 'type' => 'hidden', 'value' => $params['user_id'] ]; } $form['row'][0]['entity_type'] = [ 'type' => 'hidden', 'value' => 'device' ]; $form['row'][0]['action'] = [ 'type' => 'hidden', 'value' => $action_add ]; $form_items['devices'] = generate_form_values('device', $permissions_list, 'device_id', [ 'filter_mode' => 'exclude', 'subtext' => '%location%', 'show_disabled' => TRUE, 'show_icon' => TRUE ]); $form['row'][0]['entity_id'] = [ 'type' => 'multiselect', 'name' => 'Permit Device', 'width' => '250px', //'value' => $vars['entity_id'], 'groups' => ['', 'UP', 'DOWN', 'DISABLED'], // This is optgroup order for values (if required) 'values' => $form_items['devices'] ]; // add button $form['row'][0]['Submit'] = [ 'type' => 'submit', 'name' => 'Add', 'icon' => $config['icon']['plus'], 'right' => TRUE, 'value' => 'Add' ]; print_form($form); echo generate_box_close(); } // EOF