User logs fix failed

This commit is contained in:
FusionPBX 2024-10-10 16:28:59 -06:00 committed by GitHub
parent 8fe2199e82
commit d4ca64c2d1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 383 additions and 141 deletions

View File

@ -1,174 +1,359 @@
<?php <?php
/* /*
FusionPBX FusionPBX
Version: MPL 1.1 Version: MPL 1.1
The contents of this file are subject to the Mozilla Public License Version The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with 1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL/ http://www.mozilla.org/MPL/
Software distributed under the License is distributed on an "AS IS" basis, Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the for the specific language governing rights and limitations under the
License. License.
The Original Code is FusionPBX The Original Code is FusionPBX
The Initial Developer of the Original Code is The Initial Developer of the Original Code is
Mark J Crane <markjcrane@fusionpbx.com> Mark J Crane <markjcrane@fusionpbx.com>
Portions created by the Initial Developer are Copyright (C) 2020-2023 Portions created by the Initial Developer are Copyright (C) 2008-2024
the Initial Developer. All Rights Reserved. the Initial Developer. All Rights Reserved.
Contributor(s): Contributor(s):
Mark J Crane <markjcrane@fusionpbx.com> Mark J Crane <markjcrane@fusionpbx.com>
*/ */
//define the databases class /**
if (!class_exists('databases')) { * plugin_database
class databases { *
* @method plugin_database validates the authentication using information from the database
*/
class plugin_database {
/** /**
* declare private variables * Define variables and their scope
*/ */
private $app_name; public $domain_name;
private $app_uuid; public $domain_uuid;
private $permission_prefix; public $user_uuid;
private $list_page; public $contact_uuid;
private $table; public $contact_organization;
private $uuid_prefix; public $contact_name_given;
public $contact_name_family;
public $contact_image;
public $username;
public $password;
public $key;
public $debug;
public $user_email;
/** /**
* called when the object is created * database checks the local database to authenticate the user or key
*/ * @return array [authorized] => true or false
public function __construct() { */
function database(authentication $auth, settings $settings) {
//assign private variables //pre-process some settings
$this->app_name = 'databases'; $theme_favicon = $settings->get('theme', 'favicon', PROJECT_PATH.'/themes/default/favicon.ico');
$this->app_uuid = '8d229b6d-1383-fcec-74c6-4ce1682479e2'; $theme_logo = $settings->get('theme', 'logo', PROJECT_PATH.'/themes/default/images/logo_login.png');
$this->permission_prefix = 'database_'; $theme_login_logo_width = $settings->get('theme', 'login_logo_width', 'auto; max-width: 300px');
$this->list_page = 'databases.php'; $theme_login_logo_height = $settings->get('theme', 'login_logo_height', 'auto; max-height: 300px');
$this->table = 'databases'; $theme_message_delay = 1000 * (float)$settings->get('theme', 'message_delay', 3000);
$this->uuid_prefix = 'database_'; $background_videos = $settings->get('theme', 'background_video', null);
$theme_background_video = (isset($background_videos) && is_array($background_videos)) ? $background_videos[0] : null;
$login_domain_name_visible = $settings->get('login', 'domain_name_visible');
$login_domain_name = $settings->get('login', 'domain_name');
$login_destination = $settings->get('login', 'destination');
$users_unique = $settings->get('users', 'unique', '');
} //check if already authorized
if (isset($_SESSION['authentication']['plugin']['database']) && $_SESSION['authentication']['plugin']['database']["authorized"]) {
return;
}
/** //show the authentication code view
* delete records if (empty($_REQUEST["username"]) && empty($_REQUEST["key"])) {
*/
public function delete($records) { //get the domain
if (permission_exists($this->permission_prefix.'delete')) { $domain_array = explode(":", $_SERVER["HTTP_HOST"]);
$domain_name = $domain_array[0];
//create token
//$object = new token;
//$token = $object->create('login');
//add multi-lingual support //add multi-lingual support
$language = new text; $language = new text;
$text = $language->get(); $text = $language->get(null, '/core/authentication');
//validate the token //initialize a template object
$token = new token; $view = new template();
if (!$token->validate($_SERVER['PHP_SELF'])) { $view->engine = 'smarty';
message::add($text['message-invalid_token'],'negative'); $view->template_dir = $_SERVER["DOCUMENT_ROOT"].PROJECT_PATH.'/core/authentication/resources/views/';
header('Location: '.$this->list_page); $view->cache_dir = sys_get_temp_dir();
exit; $view->init();
//add translations
$view->assign("login_title", $text['button-login']);
$view->assign("label_username", $text['label-username']);
$view->assign("label_password", $text['label-password']);
$view->assign("label_domain", $text['label-domain']);
$view->assign("button_login", $text['button-login']);
//assign default values to the template
$view->assign("project_path", PROJECT_PATH);
$view->assign("login_destination_url", $login_destination);
$view->assign("login_domain_name_visible", $login_domain_name_visible);
$view->assign("login_domain_names", $login_domain_name);
$view->assign("favicon", $theme_favicon);
$view->assign("login_logo_width", $theme_login_logo_width);
$view->assign("login_logo_height", $theme_login_logo_height);
$view->assign("login_logo_source", $theme_logo);
$view->assign("message_delay", $theme_message_delay);
$view->assign("background_video", $theme_background_video);
if (!empty($_SESSION['username'])) {
$view->assign("login_password_description", $text['label-password_description']);
$view->assign("username", $_SESSION['username']);
$view->assign("button_cancel", $text['button-cancel']);
} }
//delete multiple records //messages
if (is_array($records) && @sizeof($records) != 0) { $view->assign('messages', message::html(true, ' '));
//build the delete array //add the token name and hash to the view
foreach ($records as $x => $record) { //$view->assign("token_name", $token['name']);
if (!empty($record['checked']) && $record['checked'] == 'true' && is_uuid($record['uuid'])) { //$view->assign("token_hash", $token['hash']);
$array[$this->table][$x][$this->uuid_prefix.'uuid'] = $record['uuid'];
}
}
//delete the checked rows //show the views
if (is_array($array) && @sizeof($array) != 0) { $content = $view->render('login.htm');
echo $content;
//execute delete exit;
$database = new database;
$database->app_name = $this->app_name;
$database->app_uuid = $this->app_uuid;
$database->delete($array);
unset($array);
//set message
message::add($text['message-delete']);
}
unset($records);
}
} }
}
/** //validate the token
* copy records //$token = new token;
*/ //if (!$token->validate($_SERVER['PHP_SELF'])) {
public function copy($records) { // message::add($text['message-invalid_token'],'negative');
if (permission_exists($this->permission_prefix.'add')) { // header('Location: domains.php');
// exit;
//}
//add multi-lingual support //add the authentication details
$language = new text; if (isset($_REQUEST["username"])) {
$text = $language->get(); $this->username = $_REQUEST["username"];
$_SESSION['username'] = $this->username;
}
if (isset($_REQUEST["password"])) {
$this->password = $_REQUEST["password"];
}
if (isset($_REQUEST["key"])) {
$this->key = $_REQUEST["key"];
}
if (isset($_REQUEST["domain_name"])) {
$domain_name = $_REQUEST["domain_name"];
$this->domain_name = $_REQUEST["domain_name"];
}
//validate the token //get the domain name
$token = new token; $auth->get_domain();
if (!$token->validate($_SERVER['PHP_SELF'])) { $this->username = $_SESSION['username'] ?? null;
message::add($text['message-invalid_token'],'negative'); //$this->domain_uuid = $_SESSION['domain_uuid'] ?? null;
header('Location: '.$this->list_page); //$this->domain_name = $_SESSION['domain_name'] ?? null;
exit;
//debug information
//echo "domain_uuid: ".$this->domain_uuid."<br />\n";
//view_array($this->domain_uuid, false);
//echo "domain_name: ".$this->domain_name."<br />\n";
//echo "username: ".$this->username."<br />\n";
//set the default status
$user_authorized = false;
//check if contacts app exists
$contacts_exists = file_exists($_SERVER["DOCUMENT_ROOT"].PROJECT_PATH.'/app/contacts/') ? true : false;
//check the username and password if they don't match then redirect to the login
$sql = "select ";
$sql .= " d.domain_name, ";
$sql .= " u.user_uuid, ";
$sql .= " u.contact_uuid, ";
$sql .= " u.username, ";
$sql .= " u.password, ";
$sql .= " u.user_email, ";
$sql .= " u.salt, ";
$sql .= " u.api_key, ";
$sql .= " u.domain_uuid ";
if ($contacts_exists) {
$sql .= ",";
$sql .= "c.contact_organization, ";
$sql .= "c.contact_name_given, ";
$sql .= "c.contact_name_family, ";
$sql .= "a.contact_attachment_uuid ";
}
$sql .= "from ";
$sql .= " v_domains as d, ";
$sql .= " v_users as u ";
if ($contacts_exists) {
$sql .= "left join v_contacts as c on u.contact_uuid = c.contact_uuid and u.contact_uuid is not null ";
$sql .= "left join v_contact_attachments as a on u.contact_uuid = a.contact_uuid and u.contact_uuid is not null and a.attachment_primary = 1 and a.attachment_filename is not null and a.attachment_content is not null ";
}
$sql .= "where ";
$sql .= " u.domain_uuid = d.domain_uuid ";
$sql .= " and (";
$sql .= " user_type = 'default' ";
$sql .= " or user_type is null";
$sql .= " ) ";
if (isset($this->key) && strlen($this->key) > 30) {
$sql .= "and u.api_key = :api_key ";
$parameters['api_key'] = $this->key;
}
else {
$sql .= "and (\n";
$sql .= " lower(u.username) = lower(:username)\n";
$sql .= " or lower(u.user_email) = lower(:username)\n";
$sql .= ")\n";
$parameters['username'] = $this->username;
}
if ($users_unique === "global") {
//unique username - global (example: email address)
}
else {
//unique username - per domain
$sql .= "and u.domain_uuid = :domain_uuid ";
$parameters['domain_uuid'] = $this->domain_uuid;
}
$sql .= "and (user_enabled = 'true' or user_enabled is null) ";
$row = $settings->database()->select($sql, $parameters, 'row');
if (!empty($row) && is_array($row) && @sizeof($row) != 0) {
//validate the password
$valid_password = false;
if (isset($this->key) && strlen($this->key) > 30 && $this->key === $row["api_key"]) {
$valid_password = true;
}
else if (substr($row["password"], 0, 1) === '$') {
if (isset($this->password) && !empty($this->password)) {
if (password_verify($this->password, $row["password"])) {
$valid_password = true;
}
}
}
else {
//deprecated - compare the password provided by the user with the one in the database
if (md5($row["salt"].$this->password) === $row["password"]) {
$row["password"] = crypt($this->password, '$1$'.$password_salt.'$');
$valid_password = true;
}
} }
//copy the checked records //set the domain and user settings
if (is_array($records) && @sizeof($records) != 0) { if ($valid_password) {
//set the domain_uuid
$this->domain_uuid = $row["domain_uuid"];
$this->domain_name = $row["domain_name"];
//get checked records //set the domain session variables
foreach ($records as $x => $record) { $_SESSION["domain_uuid"] = $this->domain_uuid;
if (!empty($record['checked']) && $record['checked'] == 'true' && is_uuid($record['uuid'])) { $_SESSION["domain_name"] = $this->domain_name;
$uuids[] = "'".$record['uuid']."'";
} //set the domain setting
if ($users_unique === "global" && $row["domain_uuid"] !== $this->domain_uuid) {
$domain = new domains();
$domain->set();
} }
//create insert array from existing data //set the variables
if (is_array($uuids) && @sizeof($uuids) != 0) { $this->user_uuid = $row['user_uuid'];
$sql = "select * from v_".$this->table." "; $this->username = $row['username'];
$sql .= "where ".$this->uuid_prefix."uuid in (".implode(', ', $uuids).") "; $this->user_email = $row['user_email'];
$database = new database; $this->contact_uuid = $row['contact_uuid'];
$rows = $database->select($sql, $parameters ?? null, 'all'); if ($contacts_exists) {
if (is_array($rows) && @sizeof($rows) != 0) { $this->contact_organization = $row['contact_organization'];
foreach ($rows as $x => $row) { $this->contact_name_given = $row['contact_name_given'];
$this->contact_name_family = $row['contact_name_family'];
//copy data $this->contact_image = $row['contact_attachment_uuid'];
$array[$this->table][$x] = $row;
//overwrite
$array[$this->table][$x][$this->uuid_prefix.'uuid'] = uuid();
$array[$this->table][$x]['database_description'] = trim($row['database_description'].' ('.$text['label-copy'].')');
}
}
unset($sql, $parameters, $rows, $row);
} }
//save the changes and set the message //debug info
if (is_array($array) && @sizeof($array) != 0) { //echo "user_uuid ".$this->user_uuid."<br />\n";
//echo "username ".$this->username."<br />\n";
//echo "contact_uuid ".$this->contact_uuid."<br />\n";
//save the array //set a few session variables
$database = new database; $_SESSION["user_uuid"] = $row['user_uuid'];
$database->app_name = $this->app_name; $_SESSION["username"] = $row['username'];
$database->app_uuid = $this->app_uuid; $_SESSION["user_email"] = $row['user_email'];
$database->save($array); $_SESSION["contact_uuid"] = $row["contact_uuid"];
unset($array);
//set message
message::add($text['message-copy']);
}
unset($records);
} }
//check to to see if the the password hash needs to be updated
if ($valid_password) {
//set the password hash cost
$options = array('cost' => 10);
//check if a newer hashing algorithm is available or the cost has changed
if (password_needs_rehash($row["password"], PASSWORD_DEFAULT, $options)) {
//build user insert array
$array = [];
$array['users'][0]['user_uuid'] = $this->user_uuid;
$array['users'][0]['domain_uuid'] = $this->domain_uuid;
$array['users'][0]['user_email'] = $this->user_email;
$array['users'][0]['password'] = password_hash($this->password, PASSWORD_DEFAULT, $options);
$array['users'][0]['salt'] = null;
//build user group insert array
$array['user_groups'][0]['user_group_uuid'] = uuid();
$array['user_groups'][0]['domain_uuid'] = $this->domain_uuid;
$array['user_groups'][0]['group_name'] = 'user';
$array['user_groups'][0]['user_uuid'] = $this->user_uuid;
//grant temporary permissions
$p = new permissions;
$p->add('user_edit', 'temp');
//execute insert
$settings->database()->app_name = 'authentication';
$settings->database()->app_uuid = 'a8a12918-69a4-4ece-a1ae-3932be0e41f1';
$settings->database()->save($array);
unset($array);
//revoke temporary permissions
$p->delete('user_edit', 'temp');
}
}
//result array
if ($valid_password) {
$result["plugin"] = "database";
$result["domain_name"] = $this->domain_name;
$result["username"] = $this->username;
$result["user_uuid"] = $this->user_uuid;
$result["domain_uuid"] = $_SESSION['domain_uuid'];
$result["contact_uuid"] = $this->contact_uuid;
if ($contacts_exists) {
$result["contact_organization"] = $this->contact_organization;
$result["contact_name_given"] = $this->contact_name_given;
$result["contact_name_family"] = $this->contact_name_family;
$result["contact_image"] = $this->contact_image;
}
$result["user_email"] = $this->user_email;
$result["sql"] = $sql;
$result["authorized"] = $valid_password;
}
//return the results
return $result ?? false;
} }
}
return;
} }
} }
?> ?>

View File

@ -259,7 +259,12 @@
foreach ($user_logs as $row) { foreach ($user_logs as $row) {
//check the session status //check the session status
$session_file = 'sess_'.$row['session_id']; $session_file = 'sess_'.$row['session_id'];
$session_status = (!empty($row['session_id']) && file_exists($session_path.'/'.$session_file)) ? 'active' : 'inactive'; if (!empty($row['result']) && $row['result'] == 'success') {
$session_status = (!empty($row['session_id']) && file_exists($session_path.'/'.$session_file)) ? 'active' : 'inactive';
}
elseif (!empty($row['result']) && $row['result'] == 'failure') {
$session_status = 'failed';
}
echo "<tr class='list-row'>\n"; echo "<tr class='list-row'>\n";
if (permission_exists('user_log_delete')) { if (permission_exists('user_log_delete')) {

View File

@ -49,8 +49,8 @@ $menu_sub_text_color = $_SESSION['theme']['menu_sub_text_color']['text'] ?? '#ff
$menu_sub_text_size = $_SESSION['theme']['menu_sub_text_size']['text'] ?? '10pt'; $menu_sub_text_size = $_SESSION['theme']['menu_sub_text_size']['text'] ?? '10pt';
$menu_sub_text_color_hover = $_SESSION['theme']['menu_sub_text_color_hover']['text'] ?? '#fd9c03'; $menu_sub_text_color_hover = $_SESSION['theme']['menu_sub_text_color_hover']['text'] ?? '#fd9c03';
$menu_sub_background_color_hover = $_SESSION['theme']['menu_sub_background_color_hover']['text'] ?? '#141414'; $menu_sub_background_color_hover = $_SESSION['theme']['menu_sub_background_color_hover']['text'] ?? '#141414';
$header_user_color_hover = $_SESSION['theme']['header_user_color_hover']['text'] ?? '#1892e6'; $header_user_color_hover = $_SESSION['theme']['header_user_color_hover']['text'] ?? null;
$header_domain_color_hover = $_SESSION['theme']['header_domain_color_hover']['text'] ?? '#1892e6'; $header_domain_color_hover = $_SESSION['theme']['header_domain_color_hover']['text'] ?? null;
$logout_icon_color = $_SESSION['theme']['logout_icon_color']['text'] ?? 'rgba(255,255,255,0.8)'; $logout_icon_color = $_SESSION['theme']['logout_icon_color']['text'] ?? 'rgba(255,255,255,0.8)';
$logout_icon_color_hover = $_SESSION['theme']['logout_icon_color_hover']['text'] ?? 'rgba(255,255,255,1.0)'; $logout_icon_color_hover = $_SESSION['theme']['logout_icon_color_hover']['text'] ?? 'rgba(255,255,255,1.0)';
$menu_main_toggle_color = $_SESSION['theme']['menu_main_toggle_color']['text'] ?? 'rgba(255,255,255,0.8)'; $menu_main_toggle_color = $_SESSION['theme']['menu_main_toggle_color']['text'] ?? 'rgba(255,255,255,0.8)';
@ -653,7 +653,8 @@ else { //default: white
} }
/* main menu item */ /* main menu item */
ul.navbar-nav > li.nav-item > a.nav-link { ul.navbar-nav > li.nav-item > a.nav-link,
ul.navbar-nav.ml-auto > li.nav-item > a.nav-link {
font-family: <?=$menu_main_text_font?>; font-family: <?=$menu_main_text_font?>;
font-size: <?=$menu_main_text_size?>; font-size: <?=$menu_main_text_size?>;
color: <?=$menu_main_text_color?>; color: <?=$menu_main_text_color?>;
@ -768,17 +769,21 @@ else { //default: white
padding: 10px; padding: 10px;
} }
<?php if (!empty($header_user_color_hover)) { ?>
ul.navbar-nav > li.nav-item:hover > a.header_user, ul.navbar-nav > li.nav-item:hover > a.header_user,
ul.navbar-nav > li.nav-item:focus > a.header_user, ul.navbar-nav > li.nav-item:focus > a.header_user,
ul.navbar-nav > li.nav-item:active > a.header_user { ul.navbar-nav > li.nav-item:active > a.header_user {
color: <?=$header_user_color_hover?>; color: <?=$header_user_color_hover?>;
} }
<?php } ?>
<?php if (!empty($header_domain_color_hover)) { ?>
ul.navbar-nav > li.nav-item:hover > a.header_domain, ul.navbar-nav > li.nav-item:hover > a.header_domain,
ul.navbar-nav > li.nav-item:focus > a.header_domain, ul.navbar-nav > li.nav-item:focus > a.header_domain,
ul.navbar-nav > li.nav-item:active > a.header_domain { ul.navbar-nav > li.nav-item:active > a.header_domain {
color: <?=$header_domain_color_hover?>; color: <?=$header_domain_color_hover?>;
} }
<?php } ?>
/* logout icon */ /* logout icon */
a.logout_icon { a.logout_icon {
@ -1045,7 +1050,7 @@ else { //default: white
/* BODY/HEADER BAR *****************************************************************/ /* BODY/HEADER BAR *****************************************************************/
<?php if ($menu_style == 'side') { ?> <?php if ($menu_style == 'side' || $menu_style == 'fixed') { ?>
div#body_header { div#body_header {
position: relative; position: relative;
z-index: 1; z-index: 1;
@ -1065,6 +1070,37 @@ else { //default: white
color: <?=$body_header_text_link_color_hover?>; color: <?=$body_header_text_link_color_hover?>;
text-decoration: none; text-decoration: none;
} }
div#body_header_user_menu {
z-index: 6;
display: none;
position: absolute;
top: 50px;
/* right: specified in /resources/classes/menu.php */
padding: 15px;
background-color: <?=$body_header_background_color?>;
border: 1px solid <?=color_adjust($body_header_shadow_color, 0.05)?>;
<?php $br = format_border_radius($dashboard_border_radius, '5px'); ?>
-webkit-border-radius: <?php echo $br['tl']['n'].$br['tl']['u']; ?> <?php echo $br['tr']['n'].$br['tr']['u']; ?> <?php echo $br['br']['n'].$br['br']['u']; ?> <?php echo $br['bl']['n'].$br['bl']['u']; ?>;
-moz-border-radius: <?php echo $br['tl']['n'].$br['tl']['u']; ?> <?php echo $br['tr']['n'].$br['tr']['u']; ?> <?php echo $br['br']['n'].$br['br']['u']; ?> <?php echo $br['bl']['n'].$br['bl']['u']; ?>;
border-radius: <?php echo $br['tl']['n'].$br['tl']['u']; ?> <?php echo $br['tr']['n'].$br['tr']['u']; ?> <?php echo $br['br']['n'].$br['br']['u']; ?> <?php echo $br['bl']['n'].$br['bl']['u']; ?>;
<?php unset($br); ?>
-webkit-box-shadow: 0 2px <?=$body_header_shadow_size ?? '7px'?> <?=$body_header_shadow_color?>;
-moz-box-shadow: 0 2px <?=$body_header_shadow_size ?? '7px'?> <?=$body_header_shadow_color?>;
box-shadow: 0 2px <?=$body_header_shadow_size ?? '7px'?> <?=$body_header_shadow_color?>;
}
@media (max-width: 575.98px) {
div#body_header_user_menu {
width: calc(100% - 20px);
/* right: specified in /resources/classes/menu.php */
}
}
div#body_header_user_menu a {
font-size: 90%;
text-decoration: none;
}
<?php } else { ?> <?php } else { ?>
div#body_header { div#body_header {
padding: 10px; padding: 10px;
@ -1452,7 +1488,7 @@ else { //default: white
#domains_container { #domains_container {
z-index: 99990; z-index: 99990;
position: absolute; position: fixed;
right: 0; right: 0;
top: 0; top: 0;
bottom: 0; bottom: 0;
@ -1747,6 +1783,7 @@ else { //default: white
input[type=number].formfld, input[type=number].formfld,
input[type=url].formfld, input[type=url].formfld,
input[type=password].formfld, input[type=password].formfld,
input[type=email].formfld,
label.formfld { label.formfld {
font-family: <?=$input_text_font?>; font-family: <?=$input_text_font?>;
font-size: <?=$input_text_size?>; font-size: <?=$input_text_size?>;
@ -1784,11 +1821,13 @@ else { //default: white
input[type=text].txt, input[type=text].txt,
input[type=number].txt, input[type=number].txt,
input[type=password].txt, input[type=password].txt,
input[type=email].txt,
textarea.formfld, textarea.formfld,
input[type=text].formfld, input[type=text].formfld,
input[type=number].formfld, input[type=number].formfld,
input[type=url].formfld, input[type=url].formfld,
input[type=password].formfld { input[type=password].formfld,
input[type=email].formfld {
transition: width 0.25s; transition: width 0.25s;
-moz-transition: width 0.25s; -moz-transition: width 0.25s;
-webkit-transition: width 0.25s; -webkit-transition: width 0.25s;
@ -1811,12 +1850,14 @@ else { //default: white
input[type=text].txt:hover, input[type=text].txt:hover,
input[type=number].txt:hover, input[type=number].txt:hover,
input[type=password].txt:hover, input[type=password].txt:hover,
input[type=email].txt:hover,
label.txt:hover, label.txt:hover,
textarea.formfld:hover, textarea.formfld:hover,
input[type=text].formfld:hover, input[type=text].formfld:hover,
input[type=number].formfld:hover, input[type=number].formfld:hover,
input[type=url].formfld:hover, input[type=url].formfld:hover,
input[type=password].formfld:hover, input[type=password].formfld:hover,
input[type=email].formfld:hover,
label.formfld:hover { label.formfld:hover {
border-color: <?=$input_border_color_hover?>; border-color: <?=$input_border_color_hover?>;
} }
@ -1825,12 +1866,14 @@ else { //default: white
input[type=text].txt:focus, input[type=text].txt:focus,
input[type=number].txt:focus, input[type=number].txt:focus,
input[type=password].txt:focus, input[type=password].txt:focus,
input[type=email].txt:focus,
label.txt:focus, label.txt:focus,
textarea.formfld:focus, textarea.formfld:focus,
input[type=text].formfld:focus, input[type=text].formfld:focus,
input[type=number].formfld:focus, input[type=number].formfld:focus,
input[type=url].formfld:focus, input[type=url].formfld:focus,
input[type=password].formfld:focus, input[type=password].formfld:focus,
input[type=email].formfld:focus,
label.formfld:focus { label.formfld:focus {
border-color: <?=$input_border_color_focus?>; border-color: <?=$input_border_color_focus?>;
/* first clear */ /* first clear */
@ -3389,7 +3432,7 @@ else { //default: white
.list-status-active { .list-status-active {
width: 10px; width: 10px;
height: 10px; height: 10px;
background-color: green; background-color: #03C04A;
border-radius: 50%; border-radius: 50%;
display: inline-block; display: inline-block;
} }
@ -3402,6 +3445,14 @@ else { //default: white
display: inline-block; display: inline-block;
} }
.list-status-failed {
width: 10px;
height: 10px;
background-color: #ea4c46;
border-radius: 50%;
display: inline-block;
}
/* EDIT ********************************************************************************/ /* EDIT ********************************************************************************/
td.edit_delete_checkbox_all { td.edit_delete_checkbox_all {
@ -3448,6 +3499,7 @@ else { //default: white
.pct-95 { width: 95%; } .pct-95 { width: 95%; }
.pct-100 { width: 100%; } .pct-100 { width: 100%; }
/* SIDE PADDING & MARGIN HELPERS **********************************************************************/ /* SIDE PADDING & MARGIN HELPERS **********************************************************************/
.pl-1 { padding-left: 1px !important; } .pr-1 { padding-right: 1px !important; } .pl-1 { padding-left: 1px !important; } .pr-1 { padding-right: 1px !important; }