From 435631f313903b53e28f3192cafdc4c10a016fc7 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 4 Oct 2022 17:13:01 -0400 Subject: [PATCH 001/162] major updates to node data tables and handling user/node data --- app/Console/Commands/NodeInfo.php | 189 +- app/Console/Helper.php | 7 +- .../Controllers/Api/V1/AdminController.php | 1609 ++++++++---- .../Controllers/Api/V1/MetricController.php | 251 +- .../Controllers/Api/V1/UserController.php | 2255 ++++++++++++++--- app/Services/NodeHelper.php | 799 +++--- ...2022_09_21_162427_create_all_node_data.php | 71 + .../2022_09_21_204420_create_mbs.php | 33 + routes/api.php | 17 + 9 files changed, 3873 insertions(+), 1358 deletions(-) create mode 100644 database/migrations/2022_09_21_162427_create_all_node_data.php create mode 100644 database/migrations/2022_09_21_204420_create_mbs.php diff --git a/app/Console/Commands/NodeInfo.php b/app/Console/Commands/NodeInfo.php index 1801a076..566441da 100644 --- a/app/Console/Commands/NodeInfo.php +++ b/app/Console/Commands/NodeInfo.php @@ -3,8 +3,6 @@ namespace App\Console\Commands; use App\Models\Metric; -use App\Models\Node; -use App\Models\NodeInfo as ModelsNodeInfo; use App\Models\User; use App\Models\UserAddress; use App\Services\NodeHelper; @@ -34,193 +32,10 @@ public function __construct() { parent::__construct(); } - + public function handle() { $nodeHelper = new NodeHelper(); - $nodeHelper->updateStats(); - $this->updateNode(); - // $this->updateUptime(); - $this->updateRank(); - } - - public function updateNode() - { - $nodes = Node::whereNotNull('protocol_version')->get(); - $max_hight_block = $nodes->max('block_height'); - $base_block = 10; - $versions = $nodes->pluck('protocol_version'); - $versions = $versions->toArray(); - usort($versions, 'version_compare'); - $highestVersion = (end($versions)); - $grouped = $nodes->groupBy('node_address'); - foreach ($grouped as $key => $values) { - $key = strtolower($key); - - $versionsNodes = $values->pluck('protocol_version'); - $versionsNodes = $versionsNodes->toArray(); - - usort($versionsNodes, 'version_compare'); - - $highestVersionNode = (end($versionsNodes)); - if (version_compare($highestVersion, $highestVersionNode, '<')) { - $userAddress = UserAddress::where('public_address_node', $key)->first(); - if ($userAddress) { - $userAddress->is_fail_node = 1; - $userAddress->node_status = 'Offline'; - $userAddress->save(); - } - } - - $totalResponsiveness = $totalBlockHeight = 0; - - $nodeInfo = ModelsNodeInfo::where('node_address', $key)->first(); - if ($nodeInfo) { - $groupedVersion = $values->groupBy('protocol_version'); - $countVersion = count($groupedVersion); - $totalArray = []; - - foreach ($groupedVersion as $ver => $items) { - $countItem = count($items); - $totalVerResponsiveness = 0; - $totalVerBlockHeight = 0; - $totalVerPeer = 0; - - foreach ($items as $item) { - $totalVerResponsiveness += $item->update_responsiveness; - $totalVerBlockHeight += $item->block_height; - $totalVerPeer += $item->peers; - } - - $totalArray[$ver] = [ - 'totalVerResponsiveness' => round($totalVerResponsiveness / $countItem), - 'totalVerBlockHeight' => round($totalVerBlockHeight / $countItem), - 'totalVerPeer' => round($totalVerPeer / $countItem), - ]; - } - foreach ($totalArray as $total) { - $totalResponsiveness += $total['totalVerResponsiveness']; - $totalBlockHeight += $total['totalVerBlockHeight']; - } - $block_height = round($totalBlockHeight / $countVersion); - $block_height_average = ($base_block - ($max_hight_block - $block_height)) * 10; - if ($block_height_average <= 0) { - $block_height_average = 0; - } - $nodeInfo->block_height_average = $block_height_average; - $nodeInfo->save(); - } - } - - $users = User::with(['addresses']) - ->where('role', 'member') - ->where('banned', 0) - ->get(); - if ($users && count($users) > 0) { - foreach ($users as $user) { - $addresses = $user->addresses ?? null; - if (!$addresses) { - $user->is_fail_node = 1; - $user->node_status = 'Offline'; - $user->save(); - } else if (count($addresses) > 0) { - $hasNotFailNode = $hasNotOfflineStatus = false; - foreach ($addresses as $address) { - if ($address->is_fail_node != 1) { - $hasNotFailNode = true; - } - if ($address->node_status != 'Offline') { - $hasNotOfflineStatus = true; - } - } - if (!$hasNotFailNode) { - $user->is_fail_node = 1; - $user->node_status = 'Offline'; - $user->save(); - } else if (!$hasNotOfflineStatus) { - $user->node_status = 'Offline'; - $user->save(); - } - } - } - } - } - - public function updateUptime() - { - $nodes = ModelsNodeInfo::get(); - $now = Carbon::now('UTC'); - $time = $now->subDays(14); - foreach ($nodes as $node) { - $avg_uptime = Node::where('node_address', strtolower($node->node_address)) - ->whereNotNull('uptime') - ->where('created_at', '>=', $time) - ->avg('uptime'); - $node->uptime = $avg_uptime * 100; - $node->save(); - } - } - - public function updateRank() - { - $slide_value_uptime = $slide_value_update_responsiveness = $slide_value_delegotors = $slide_value_stake_amount = $slide_delegation_rate = 20; - - $max_uptime = Node::max('uptime'); - $max_uptime = $max_uptime * 100; - $max_delegators = ModelsNodeInfo::max('delegators_count'); - $max_stake_amount = ModelsNodeInfo::max('total_staked_amount'); - - DB::table('user_addresses')->update(['rank' => null]); - $userAddresses = UserAddress::with(['user', 'user.metric', 'user.nodeInfo']) - ->leftJoin('node_info', 'user_addresses.public_address_node', '=', 'node_info.node_address') - ->select([ - 'user_addresses.*', - 'node_info.delegation_rate', - 'node_info.delegators_count', - 'node_info.total_staked_amount', - ]) - ->whereHas('user') - ->get(); - - foreach ($userAddresses as $userAddress) { - $latest = Node::where('node_address', strtolower($userAddress->public_address_node))->whereNotnull('protocol_version')->orderBy('created_at', 'desc')->first(); - if (!$latest) $latest = new Node(); - $delegation_rate = $userAddress->delegation_rate ? $userAddress->delegation_rate / 100 : 1; - if (!$userAddress->user->metric && !$userAddress->user->nodeInfo) { - $userAddress->totalScore = null; - continue; - } - $latest_uptime_node = isset($latest->uptime) ? $latest->uptime * 100 : null; - $latest_update_responsiveness_node = $latest->update_responsiveness ?? null; - $metric = $userAddress->user->metric; - if (!$metric) $metric = new Metric(); - - $latest_uptime_metric = $metric->uptime ? $metric->uptime : null; - $latest_update_responsiveness_metric = $metric->update_responsiveness ? $metric->update_responsiveness : null; - - $latest_uptime = $latest_uptime_node ?? $latest_uptime_metric ?? 1; - $latest_update_responsiveness = $latest_update_responsiveness_node ?? $latest_update_responsiveness_metric ?? 1; - - $delegators_count = $userAddress->delegators_count ? $userAddress->user->nodeInfo->delegators_count : 0; - $total_staked_amount = $userAddress->total_staked_amount ? $userAddress->user->nodeInfo->total_staked_amount : 0; - - $uptime_score = ($slide_value_uptime * $latest_uptime) / 100; - $update_responsiveness_score = ($slide_value_update_responsiveness * $latest_update_responsiveness) / 100; - $dellegator_score = ($delegators_count / $max_delegators) * $slide_value_delegotors; - $stake_amount_score = ($total_staked_amount / $max_stake_amount) * $slide_value_stake_amount; - $delegation_rate_score = ($slide_delegation_rate * (1 - $delegation_rate)) / 100; - $totalScore = $uptime_score + $update_responsiveness_score + $dellegator_score + $stake_amount_score + $delegation_rate_score; - - $userAddress->totalScore = $totalScore; - } - $userAddresses = $userAddresses->sortByDesc('totalScore')->values(); - foreach ($userAddresses as $key => $userAddress) { - UserAddress::where('id', $userAddress->id)->update(['rank' => $key + 1]); - $user = User::where('public_address_node', $userAddress->public_address_node)->first(); - if ($user) { - $user->rank = $key + 1; - $user->save(); - } - } + $nodeHelper->getValidatorStanding(); } } \ No newline at end of file diff --git a/app/Console/Helper.php b/app/Console/Helper.php index 0c3a07a2..2ec1af9c 100644 --- a/app/Console/Helper.php +++ b/app/Console/Helper.php @@ -44,6 +44,7 @@ public static function publicKeyToAccountHash($public_key) public static function getAccountInfoStandard($user) { $vid = strtolower($user->public_address_node ?? ''); + if (!$vid) return; // convert to account hash @@ -86,7 +87,11 @@ public static function getAccountInfoStandard($user) $response = curl_exec($curl); $decodedResponse = []; - if ($response) $decodedResponse = json_decode($response, true); + + if ($response) { + $decodedResponse = json_decode($response, true); + } + $parsed = $decodedResponse['result']['stored_value']['CLValue']['parsed'] ?? ''; $json = array(); diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 83410f5a..9da1d6cc 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -42,6 +42,8 @@ use App\Models\Vote; use App\Models\VoteResult; +use App\Services\NodeHelper; + use Illuminate\Http\Request; use Illuminate\Http\Response; @@ -59,77 +61,510 @@ class AdminController extends Controller { + public function getNodesPage() { + // Get current era + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + // Define complete return object + $return = array( + "mbs" => 0, + "ranking" => array( + "0123456789abcdef" => 0 + ), + "addresses" => array( + "0123456789abcdef" => array( + "stake_amount" => 0, + "delegators" => 0, + "uptime" => 0, + "update_responsiveness" => 100, + "peers" => 0, + "daily_earning" => 0, + "total_eras" => 0, + "eras_sinse_bad_mark" => $current_era_id, + "total_bad_marks" => 0, + "faling" => 0, + "validator_rewards" => array( + "day" => array(), + "week" => array(), + "month" => array(), + "year" => array() + ) + ) + ) + ); + + unset($return["ranking"]["0123456789abcdef"]); + unset($return["addresses"]["0123456789abcdef"]); + $nodeHelper = new NodeHelper(); + + // get ranking + $ranking = DB::select(" + SELECT + public_key, + historical_performance AS uptime, + bid_delegators_count, + bid_delegation_rate, + bid_total_staked_amount + FROM all_node_data + WHERE in_curent_era = 1 + AND in_next_era = 1 + AND in_auction = 1 + AND bad_mark = 0 + "); + $max_delegators = 0; + $max_stake_amount = 0; + + foreach ($ranking as $r) { + if ((int)$r->bid_delegators_count > $max_delegators) { + $max_delegators = (int)$r->bid_delegators_count; + } + + if ((int)$r->bid_total_staked_amount > $max_stake_amount) { + $max_stake_amount = (int)$r->bid_total_staked_amount; + } + } + + foreach ($ranking as $r) { + $uptime_score = ( + 25 * (float)$r->uptime + ) / 100; + $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; + + $fee_score = ( + 25 * + (1 - ((float)$r->bid_delegation_rate / 100)) + ); + $fee_score = $fee_score < 0 ? 0 : $fee_score; + + $count_score = ( + (float)$r->bid_delegators_count / + $max_delegators + ) * 25; + $count_score = $count_score < 0 ? 0 : $count_score; + + $stake_score = ( + (float)$r->bid_total_staked_amount / + $max_stake_amount + ) * 25; + $stake_score = $stake_score < 0 ? 0 : $stake_score; + + $return["ranking"][$r->public_key] = ( + $uptime_score + + $fee_score + + $count_score + + $stake_score + ); + } + + uasort( + $return["ranking"], + function($x, $y) { + if ($x == $y) { + return 0; + } + + return ($x > $y) ? -1 : 1; + } + ); + + $sorted_ranking = array(); + $i = 1; + + foreach ($return["ranking"] as $public_key => $score) { + $sorted_ranking[$public_key] = $i; + $i += 1; + } + + $return["ranking"] = $sorted_ranking; + + // get user addresses + $addresses = DB::select(" + SELECT + a.public_key, a.bid_delegators_count, + a.bid_total_staked_amount, a.bid_self_staked_amount, + a.historical_performance AS uptime, + a.port8888_peers AS peers, + a.bid_inactive, a.bad_mark, a.stable + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + JOIN users AS c + ON b.user_id = c.id + WHERE a.era_id = $current_era_id + "); + + if (!$addresses) { + $addresses = array(); + } + + // for each member's node address + foreach ($addresses as $address) { + $a = $address->public_key ?? ''; + + $eras_sinse_bad_mark = DB::select(" + SELECT a.era_id + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.public_key = '$a' + AND a.bad_mark = 1 + ORDER BY era_id DESC + LIMIT 1 + "); + $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; + $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; + + $total_bad_marks = DB::select(" + SELECT bad_mark + FROM all_node_data + WHERE bad_mark = 1 + AND public_key = '$a' + "); + + $total_eras = DB::select(" + SELECT era_id + FROM all_node_data + WHERE public_key = '$a' + ORDER BY era_id ASC + LIMIT 1 + "); + + $total_eras = (int)($total_eras[0]->era_id ?? 0); + $total_eras = $current_era_id - $total_eras; + + // Calc earning + $one_day_ago = Carbon::now('UTC')->subHours(24); + $daily_earning = DB::select(" + SELECT bid_self_staked_amount + FROM all_node_data + WHERE public_key = '$a' + AND created_at < '$one_day_ago' + ORDER BY era_id DESC + LIMIT 1 + "); + $daily_earning = $daily_earning[0]->bid_self_staked_amount ?? 0; + $daily_earning = $address->bid_self_staked_amount - $daily_earning; + $daily_earning = $daily_earning < 0 ? 0 : $daily_earning; + + $earning_day = $nodeHelper->getValidatorRewards($a, 'day'); + $earning_week = $nodeHelper->getValidatorRewards($a, 'week'); + $earning_month = $nodeHelper->getValidatorRewards($a, 'month'); + $earning_year = $nodeHelper->getValidatorRewards($a, 'year'); + + if ( + $address->bad_mark == 1 || + $address->bid_inactive == 1 + ) { + $failing = 1; + } else { + $failing = 0; + } + + $return["addresses"][$a] = array( + "stake_amount" => $address->bid_total_staked_amount, + "delegators" => $address->bid_delegators_count, + "uptime" => $address->uptime, + "update_responsiveness" => 100, + "peers" => $address->peers, + "daily_earning" => $daily_earning, + "total_eras" => $total_eras, + "eras_sinse_bad_mark" => $eras_sinse_bad_mark, + "total_bad_marks" => count($total_bad_marks ?? array()), + "failing" => $failing, + "validator_rewards" => array( + "day" => $earning_day, + "week" => $earning_week, + "month" => $earning_month, + "year" => $earning_year + ) + ); + } + + // get mbs + $mbs = DB::select(" + SELECT mbs + FROM mbs + ORDER BY era_id DESC + LIMIT 1 + "); + $return["mbs"] = (int)($mbs[0]->mbs ?? 0); + + info($return); + return $this->successResponse($return); + } + public function getUsers(Request $request) { - $limit = $request->limit ?? 50; - $sort_key = $request->sort_key ?? ''; - $sort_direction = $request->sort_direction ?? ''; - if (!$sort_key) $sort_key = 'created_at'; - if (!$sort_direction) $sort_direction = 'desc'; + $users = DB::select(" + SELECT + a.id, a.first_name, a.last_name, a.email, + a.pseudonym, a.telegram, a.email_verified_at, + a.entity_name, a.last_login_at, a.created_at, + a.signature_request_id, a.node_verified_at, + a.member_status, a.kyc_verified_at, + b.dob, b.country_citizenship, b.country_residence, + b.status AS profile_status, b.extra_status, + b.type, b.casper_association_kyc_hash, + b.blockchain_name, b.blockchain_desc, + c.bid_delegators_count, + c.bid_delegation_rate, + c.bid_self_staked_amount, + c.bid_total_staked_amount + FROM users AS a + JOIN profile AS b + ON a.id = b.user_id + JOIN user_addresses + ON user_addresses.user_id = a.id + JOIN all_node_data AS c + ON c.public_key = user_addresses.public_address_node + "); + + if ($users) { + foreach ($users as &$user) { + $status = 'Not Verified'; + + if ($user->profile_status == 'approved') { + $status = 'Verified'; + + if ($user->extra_status) { + $status = $user->extra_status; + } + } + + $user->membership_status = $status; + } + } + info($users); + //// done + + + + + + $limit = $request->limit ?? 50; + $sort_key = $request->sort_key ?? 'created_at'; + $sort_direction = $request->sort_direction ?? 'desc'; + $users = User::where('role', 'member') - ->with(['profile']) - ->leftJoin('node_info', 'users.public_address_node', '=', 'node_info.node_address') - ->select([ - 'users.*', - 'node_info.delegation_rate', - 'node_info.delegators_count', - 'node_info.self_staked_amount', - 'node_info.total_staked_amount', - ]) - ->get(); + ->with(['profile']) + ->leftJoin('node_info', 'users.public_address_node', '=', 'node_info.node_address') + ->select([ + 'users.*', + 'node_info.delegation_rate', + 'node_info.delegators_count', + 'node_info.self_staked_amount', + 'node_info.total_staked_amount', + ]) + ->get(); + foreach ($users as $user) { $status = 'Not Verified'; + if ($user->profile && $user->profile->status == 'approved') { $status = 'Verified'; - if ($user->profile->extra_status) + + if ($user->profile->extra_status) { $status = $user->profile->extra_status; + } } + $user->membership_status = $status; } - if ($sort_direction == 'desc') $users = $users->sortByDesc($sort_key)->values(); - else $users = $users->sortBy($sort_key)->values(); - $users = Helper::paginate($users, $limit, $request->page); - $users = $users->toArray(); + + if ($sort_direction == 'desc') { + $users = $users->sortByDesc($sort_key)->values(); + } else { + $users = $users->sortBy($sort_key)->values(); + } + + $users = Helper::paginate($users, $limit, $request->page); + $users = $users->toArray(); $users['data'] = (collect($users['data'])->values()); + return $this->successResponse($users); } public function getUserDetail($id) { $user = User::where('id', $id)->first(); - if (!$user || $user->role == 'admin') - return $this->errorResponse(__('api.error.not_found'), Response::HTTP_NOT_FOUND); - $user = $user->load(['profile', 'shuftipro', 'shuftiproTemp']); - + + if (!$user || $user->role == 'admin') { + return $this->errorResponse( + __('api.error.not_found'), + Response::HTTP_NOT_FOUND + ); + } + + $user = $user->load(['profile', 'shuftipro', 'shuftiproTemp']); $status = 'Not Verified'; - if ($user->profile && $user->profile->status == 'approved') { + + if ( + $user->profile && + $user->profile->status == 'approved' + ) { $status = 'Verified'; - if ($user->profile->extra_status) + + if ($user->profile->extra_status) { $status = $user->profile->extra_status; + } } + $user->membership_status = $status; - $user->metric = Helper::getNodeInfo($user); + $user->metric = Helper::getNodeInfo($user); return $this->successResponse($user); } public function infoDashboard(Request $request) { - $timeframe_perk = $request->timeframe_perk ?? 'last_7days'; - $timeframe_comments = $request->timeframe_comments ?? 'last_7days'; + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + // define return object + $return = array( + "total_users" => 0, + "total_stake" => 0, + "total_delegators" => 0, + "avg_uptime" => 0, + "avg_responsiveness" => 0, + "peers" => 0, + "new_users_ready" => 0, + "id_to_review" => 0, + "failing_nodes" => 0, + "perks_active" => 0, + "perks_views" => 0, + "new_comments" => 0, + "new_threads" => 0 + ); + + // get total users + $total_users = DB::select(" + SELECT pseudonym + FROM users + WHERE role = 'member' + "); + $total_users = $total_users ? count($total_users) : 0; + + // get total member stake + $total_stake = DB::select(" + SELECT + SUM(a.bid_total_staked_amount) + FROM all_node_data AS a + LEFT JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.era_id = $current_era_id + AND b.user_id IS NOT NULL + "); + $total_stake = (array)($total_stake[0] ?? array()); + $total_stake = (int)($total_stake['SUM(a.bid_total_staked_amount)'] ?? 0); + + // get total delegators across all member nodes + $total_delegators = DB::select(" + SELECT + SUM(a.bid_delegators_count) + FROM all_node_data AS a + LEFT JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.era_id = $current_era_id + AND b.user_id IS NOT NULL + "); + $total_delegators = (array)($total_delegators[0] ?? array()); + $total_delegators = (int)($total_delegators['SUM(a.bid_delegators_count)'] ?? 0); + + // get avg uptime of all user nodes + $uptime_nodes = DB::select(" + SELECT + SUM(a.historical_performance) AS numerator, + COUNT(a.historical_performance) AS denominator + FROM all_node_data AS a + LEFT JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE era_id = $current_era_id + AND b.user_id IS NOT NULL + "); + $avg_uptime = (array)($uptime_nodes[0] ?? array()); + $avg_uptime = ( + ($avg_uptime['numerator'] ?? 0) / + ($avg_uptime['denominator'] ? $avg_uptime['denominator'] : 1) + ); + + // get avg responsiveness + $avg_responsiveness = 100; + + // get max peers + $max_peers = DB::select(" + SELECT MAX(a.port8888_peers) AS max_peers + FROM all_node_data AS a + LEFT JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE era_id = $current_era_id + AND b.user_id IS NOT NULL + "); + $max_peers = (array)($max_peers[0] ?? array()); + $max_peers = $max_peers['max_peers'] ?? 0; + + // get new users ready for admin review + $new_users_ready = User::where('banned', 0) + ->where('role', 'member') + ->where(function ($q) { + $q->where('users.node_verified_at', null) + ->orWhere('users.letter_verified_at', null) + ->orWhere('users.signature_request_id', null); + })->count(); + + // get new users ready for kyc review + $id_to_review = User::where('users.role', 'member') + ->where('banned', 0) + ->join('profile', function ($query) { + $query->on('profile.user_id', '=', 'users.id') + ->where('profile.status', 'pending'); + }) + ->join('shuftipro', 'shuftipro.user_id', '=', 'users.id') + ->count(); + + // get failing member nodes + $failing_nodes = DB::select(" + SELECT + a.bid_inactive, a.bad_mark + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.era_id = $current_era_id + AND b.user_id IS NOT NULL + AND ( + a.bid_inactive = 1 OR + a.bad_mark = 1 + ) + "); + + $timeframe_perk = $request->timeframe_perk ?? 'last_7days'; + $timeframe_comments = $request->timeframe_comments ?? 'last_7days'; $timeframe_discussions = $request->timeframe_discussions ?? 'last_7days'; + // last_24hs, last_7days, last_30days, last_year - if ($timeframe_perk == 'last_24hs') + if ($timeframe_perk == 'last_24hs') { $timeframe_perk = Carbon::now('UTC')->subHours(24); - else if ($timeframe_perk == 'last_30days') + } else if ($timeframe_perk == 'last_30days') { $timeframe_perk = Carbon::now('UTC')->subDays(30); - else if ($timeframe_perk == 'last_year') + } else if ($timeframe_perk == 'last_year') { $timeframe_perk = Carbon::now('UTC')->subYear(); - else + } else { $timeframe_perk = Carbon::now('UTC')->subDays(7); + } - if ($timeframe_comments == 'last_24hs') + if ($timeframe_comments == 'last_24hs') { $timeframe_comments = Carbon::now('UTC')->subHours(24); - else if ($timeframe_comments == 'last_30days') { + } else if ($timeframe_comments == 'last_30days') { $timeframe_comments = Carbon::now('UTC')->subDays(30); } else if ($timeframe_comments == 'last_year') { $timeframe_comments = Carbon::now('UTC')->subYear(); @@ -147,76 +582,59 @@ public function infoDashboard(Request $request) $timeframe_discussions = Carbon::now('UTC')->subDays(7); } - $totalUser = User::where('role', 'member')->count(); - $toTalStake = NodeInfo::sum('total_staked_amount'); - $totalDelagateer = NodeInfo::sum('delegators_count'); - $totalNewUserReady = User::where('banned', 0) - ->where('role', 'member') - ->where(function ($q) { - $q->where('users.node_verified_at', null) - ->orWhere('users.letter_verified_at', null) - ->orWhere('users.signature_request_id', null); - })->count(); - - $totalUserVerification = User::where('users.role', 'member') - ->where('banned', 0) - ->join('profile', function ($query) { - $query->on('profile.user_id', '=', 'users.id') - ->where('profile.status', 'pending'); - }) - ->join('shuftipro', 'shuftipro.user_id', '=', 'users.id') - ->count(); - $totalFailNode = User::where('banned', 0)->whereNotNull('public_address_node')->where('is_fail_node', 1)->count(); - - $totalPerksActive = Perk::where('status', 'active')->where('created_at', '>=', $timeframe_perk)->count(); - $totalPerksViews = Perk::where('status', 'active')->where('created_at', '>=', $timeframe_perk)->sum('total_views'); - - $totalNewComments = DiscussionComment::where('created_at', '>=', $timeframe_comments)->count(); - $totalNewDiscussions = Discussion::where('created_at', '>=', $timeframe_discussions)->count(); - - $uptime_nodes = NodeInfo::whereNotNull('uptime')->pluck('uptime'); - $uptime_metrics = Metric::whereNotNull('uptime')->pluck('uptime'); - - $blocks_hight_nodes = NodeInfo::whereNotNull('block_height_average')->pluck('block_height_average'); - $blocks_hight_metrics = Metric::whereNotNull('block_height_average')->pluck('block_height_average'); - $total_blocks_hight_metrics = 0; - $base_block = 10; - foreach ($blocks_hight_metrics as $value) { - $avg = ($base_block - $value) * 10; - if ($avg > 0) { - $total_blocks_hight_metrics += $avg; - } - } - - $responsiveness_nodes = NodeInfo::whereNotNull('update_responsiveness')->pluck('update_responsiveness'); - $responsiveness_metrics = Metric::whereNotNull('update_responsiveness')->pluck('update_responsiveness'); - - $countUptime = count($uptime_nodes) + count($uptime_metrics) > 0 ? count($uptime_nodes) + count($uptime_metrics) : 1; - $count_responsiveness_nodes = count($responsiveness_nodes) + count($responsiveness_metrics); - $count_responsiveness_nodes = $count_responsiveness_nodes > 0 ? $count_responsiveness_nodes : 1; - $count_blocks_hight = (count($blocks_hight_nodes) + count($blocks_hight_metrics)) > 0 ? (count($blocks_hight_nodes) + count($blocks_hight_metrics)) : 1; - $response['totalUser'] = $totalUser; - $response['totalStake'] = $toTalStake; - $response['totalDelegators'] = $totalDelagateer; - $response['totalNewUserReady'] = $totalNewUserReady; - $response['totalUserVerification'] = $totalUserVerification; - $response['totalFailNode'] = $totalFailNode; - $response['totalPerksActive'] = $totalPerksActive; - $response['totalPerksViews'] = $totalPerksViews; - $response['totalNewComments'] = $totalNewComments; - $response['totalNewDiscussions'] = $totalNewDiscussions; - $response['avgUptime'] = ($uptime_nodes->sum() + $uptime_metrics->sum()) / $countUptime; - $response['avgBlockHeightAverage'] = ($blocks_hight_nodes->sum() + $total_blocks_hight_metrics) / $count_blocks_hight; - $response['avgUpdateResponsiveness'] = ($responsiveness_nodes->sum() + $responsiveness_metrics->sum()) / $count_responsiveness_nodes; - return $this->successResponse($response); + // get active perks + $perks_active = Perk::where('status', 'active') + ->where('created_at', '>=', $timeframe_perk) + ->count(); + + // get total perk views + $perks_views = Perk::where('status', 'active') + ->where('created_at', '>=', $timeframe_perk) + ->sum('total_views'); + + // get comments + $new_comments = DiscussionComment::where( + 'created_at', + '>=', + $timeframe_comments + )->count(); + + // get discussions + $new_threads = Discussion::where( + 'created_at', + '>=', + $timeframe_discussions + )->count(); + + + $return["total_users"] = $total_users; + $return["total_stake"] = $total_stake; + $return["total_delegators"] = $total_delegators; + $return["avg_uptime"] = $avg_uptime; + $return["avg_responsiveness"] = $avg_responsiveness; + $return["peers"] = $max_peers; + $return["new_users_ready"] = $new_users_ready; + $return["id_to_review"] = $id_to_review; + $return["failing_nodes"] = $failing_nodes; + $return["perks_active"] = $perks_active; + $return["perks_views"] = $perks_views; + $return["new_comments"] = $new_comments; + $return["new_threads"] = $new_threads; + + return $this->successResponse($return); } public function getKYC($id) { $user = User::with(['shuftipro', 'profile'])->where('id', $id)->first(); + if (!$user || $user->role == 'admin') { - return $this->errorResponse(__('api.error.not_found'), Response::HTTP_NOT_FOUND); + return $this->errorResponse( + __('api.error.not_found'), + Response::HTTP_NOT_FOUND + ); } + $response = $user->load(['profile', 'shuftipro']); return $this->successResponse($response); } @@ -227,8 +645,16 @@ public function getIntakes(Request $request) $limit = $request->limit ?? 50; $search = $request->search ?? ''; $users = User::select([ - 'id', 'email', 'node_verified_at', 'letter_verified_at', 'signature_request_id', 'created_at', - 'first_name', 'last_name', 'letter_file', 'letter_rejected_at' + 'id', + 'email', + 'node_verified_at', + 'letter_verified_at', + 'signature_request_id', + 'created_at', + 'first_name', + 'last_name', + 'letter_file', + 'letter_rejected_at' ]) ->where('banned', 0) ->where('role', 'member') @@ -238,7 +664,9 @@ public function getIntakes(Request $request) ->orWhere('users.signature_request_id', null); }) ->where(function ($query) use ($search) { - if ($search) $query->where('users.email', 'like', '%' . $search . '%'); + if ($search) { + $query->where('users.email', 'like', '%' . $search . '%'); + } }) ->orderBy('users.id', 'desc') ->paginate($limit); @@ -253,24 +681,24 @@ public function submitBallot(Request $request) $user = auth()->user(); // Validator $validator = Validator::make($request->all(), [ - 'title' => 'required', + 'title' => 'required', 'description' => 'required', - // 'time' => 'required', - // 'time_unit' => 'required|in:minutes,hours,days', - 'files' => 'array', - 'files.*' => 'file|max:10240|mimes:pdf,docx,doc,txt,rtf', - 'start_date' => 'required', - 'start_time' => 'required', - 'end_date' => 'required', - 'end_time' => 'required', + 'files' => 'array', + 'files.*' => 'file|max:10240|mimes:pdf,docx,doc,txt,rtf', + 'start_date' => 'required', + 'start_time' => 'required', + 'end_date' => 'required', + 'end_time' => 'required', ]); + if ($validator->fails()) { return $this->validateResponse($validator->errors()); } - $time = $request->time; + $time = $request->time; $timeUnit = $request->time_unit; - $mins = 0; + $mins = 0; + if ($timeUnit == 'minutes') { $mins = $time; } else if ($timeUnit == 'hours') { @@ -278,63 +706,66 @@ public function submitBallot(Request $request) } else if ($timeUnit == 'days') { $mins = $time * 60 * 24; } - $start = Carbon::createFromFormat("Y-m-d H:i:s", Carbon::now('UTC'), "UTC"); - $now = Carbon::now('UTC'); - $timeEnd = $start->addMinutes($mins); - - $endTime = $request->end_date . ' ' . $request->end_time; + + $start = Carbon::createFromFormat("Y-m-d H:i:s", Carbon::now('UTC'), "UTC"); + $now = Carbon::now('UTC'); + $timeEnd = $start->addMinutes($mins); + $endTime = $request->end_date . ' ' . $request->end_time; $endTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $endTime, 'EST'); $endTimeCarbon->setTimezone('UTC'); $ballot = new Ballot(); - $ballot->user_id = $user->id; - $ballot->title = $request->title; + $ballot->user_id = $user->id; + $ballot->title = $request->title; $ballot->description = $request->description; - // $ballot->time = $time; - // $ballot->time_unit = $timeUnit; - // $ballot->time_end = $timeEnd; - $ballot->time_end = $endTimeCarbon; - $ballot->start_date = $request->start_date; - $ballot->start_time = $request->start_time; - $ballot->end_date = $request->end_date; - $ballot->end_time = $request->end_time; - $ballot->status = 'active'; - $ballot->created_at = $now; + $ballot->time_end = $endTimeCarbon; + $ballot->start_date = $request->start_date; + $ballot->start_time = $request->start_time; + $ballot->end_date = $request->end_date; + $ballot->end_time = $request->end_time; + $ballot->status = 'active'; + $ballot->created_at = $now; $ballot->save(); + $vote = new Vote(); - $vote->ballot_id = $ballot->id; + $vote->ballot_id = $ballot->id; $vote->save(); + if ($request->hasFile('files')) { $files = $request->file('files'); + foreach ($files as $file) { - $name = $file->getClientOriginalName(); - $extension = $file->getClientOriginalExtension(); - $filenamehash = md5(Str::random(10) . '_' . (string)time()); + $name = $file->getClientOriginalName(); + $extension = $file->getClientOriginalExtension(); + $filenamehash = md5(Str::random(10) . '_' . (string)time()); $fileNameToStore = $filenamehash . '.' . $extension; // S3 file upload $S3 = new S3Client([ - 'version' => 'latest', - 'region' => getenv('AWS_DEFAULT_REGION'), + 'version' => 'latest', + 'region' => getenv('AWS_DEFAULT_REGION'), 'credentials' => [ - 'key' => getenv('AWS_ACCESS_KEY_ID'), - 'secret' => getenv('AWS_SECRET_ACCESS_KEY'), + 'key' => getenv('AWS_ACCESS_KEY_ID'), + 'secret' => getenv('AWS_SECRET_ACCESS_KEY'), ], ]); $s3result = $S3->putObject([ - 'Bucket' => getenv('AWS_BUCKET'), - 'Key' => 'perks/' . $fileNameToStore, + 'Bucket' => getenv('AWS_BUCKET'), + 'Key' => 'perks/' . $fileNameToStore, 'SourceFile' => $file ]); - // $ObjectURL = 'https://'.getenv('AWS_BUCKET') . '.s3.amazonaws.com/perks/'.$fileNameToStore; - $ObjectURL = $s3result['ObjectURL'] ?? getenv('SITE_URL') . '/not-found'; - $ballotFile = new BallotFile(); + $ObjectURL = ( + $s3result['ObjectURL'] ?? + getenv('SITE_URL') . '/not-found') + ; + + $ballotFile = new BallotFile(); $ballotFile->ballot_id = $ballot->id; - $ballotFile->name = $name; - $ballotFile->path = $ObjectURL; - $ballotFile->url = $ObjectURL; + $ballotFile->name = $name; + $ballotFile->path = $ObjectURL; + $ballotFile->url = $ObjectURL; $ballotFile->save(); } } @@ -343,7 +774,12 @@ public function submitBallot(Request $request) return $this->metaSuccess(); } catch (\Exception $ex) { DB::rollBack(); - return $this->errorResponse('Submit ballot fail', Response::HTTP_BAD_REQUEST, $ex->getMessage()); + + return $this->errorResponse( + 'Submit ballot fail', + Response::HTTP_BAD_REQUEST, + $ex->getMessage() + ); } } @@ -355,8 +791,6 @@ public function editBallot($id, Request $request) $validator = Validator::make($request->all(), [ 'title' => 'nullable', 'description' => 'nullable', - // 'time' => 'nullable', - // 'time_unit' => 'nullable|in:minutes,hours,days', 'files' => 'array', 'files.*' => 'file|max:10240|mimes:pdf,docx,doc,txt,rtf', 'file_ids_remove' => 'array', @@ -365,159 +799,171 @@ public function editBallot($id, Request $request) 'end_date' => 'required', 'end_time' => 'required', ]); + if ($validator->fails()) { return $this->validateResponse($validator->errors()); } - $time = $request->time; + + $time = $request->time; $timeUnit = $request->time_unit; - $ballot = Ballot::where('id', $id)->first(); + $ballot = Ballot::where('id', $id)->first(); + if (!$ballot) { return $this->errorResponse('Not found ballot', Response::HTTP_BAD_REQUEST); } - if ($request->title) $ballot->title = $request->title; - if ($request->description) $ballot->description = $request->description; - $now = Carbon::now('UTC'); + if ($request->title) { + $ballot->title = $request->title; + } + + if ($request->description) { + $ballot->description = $request->description; + } + + $now = Carbon::now('UTC'); $ballot->created_at = $now; - $ballot->time_end = $request->end_date . ' ' . $request->end_time; + $ballot->time_end = $request->end_date . ' ' . $request->end_time; $ballot->start_date = $request->start_date; $ballot->start_time = $request->start_time; - $ballot->end_date = $request->end_date; - $ballot->end_time = $request->end_time; - - /* - if($time && $timeUnit && ($time != $ballot->time || $timeUnit != $ballot->time_unit)) { - $mins = 0; - if ($timeUnit == 'minutes') { - $mins = $time; - } else if ($timeUnit == 'hours') { - $mins = $time * 60; - } else if ($timeUnit == 'days') { - $mins = $time * 60 * 24; - } - $start = Carbon::createFromFormat("Y-m-d H:i:s", Carbon::now('UTC'), "UTC"); - $now = Carbon::now('UTC'); - $timeEnd = $start->addMinutes($mins); - $ballot->time = $time; - $ballot->time_unit = $timeUnit; - $ballot->created_at = $now; - $ballot->time_end = $timeEnd; - } - */ - + $ballot->end_date = $request->end_date; + $ballot->end_time = $request->end_time; $ballot->save(); + if ($request->hasFile('files')) { $files = $request->file('files'); + foreach ($files as $file) { - $name = $file->getClientOriginalName(); - $extension = $file->getClientOriginalExtension(); - $filenamehash = md5(Str::random(10) . '_' . (string)time()); + $name = $file->getClientOriginalName(); + $extension = $file->getClientOriginalExtension(); + $filenamehash = md5(Str::random(10) . '_' . (string)time()); $fileNameToStore = $filenamehash . '.' . $extension; // S3 file upload $S3 = new S3Client([ - 'version' => 'latest', - 'region' => getenv('AWS_DEFAULT_REGION'), + 'version' => 'latest', + 'region' => getenv('AWS_DEFAULT_REGION'), 'credentials' => [ - 'key' => getenv('AWS_ACCESS_KEY_ID'), - 'secret' => getenv('AWS_SECRET_ACCESS_KEY'), + 'key' => getenv('AWS_ACCESS_KEY_ID'), + 'secret' => getenv('AWS_SECRET_ACCESS_KEY'), ], ]); $s3result = $S3->putObject([ - 'Bucket' => getenv('AWS_BUCKET'), - 'Key' => 'perks/'.$fileNameToStore, + 'Bucket' => getenv('AWS_BUCKET'), + 'Key' => 'perks/'.$fileNameToStore, 'SourceFile' => $file ]); - // $ObjectURL = 'https://'.getenv('AWS_BUCKET').'.s3.amazonaws.com/perks/'.$fileNameToStore; - $ObjectURL = $s3result['ObjectURL'] ?? getenv('SITE_URL').'/not-found'; - $ballotFile = new BallotFile(); + $ObjectURL = ( + $s3result['ObjectURL'] ?? + getenv('SITE_URL').'/not-found' + ); + + $ballotFile = new BallotFile(); $ballotFile->ballot_id = $ballot->id; - $ballotFile->name = $name; - $ballotFile->path = $ObjectURL; - $ballotFile->url = $ObjectURL; + $ballotFile->name = $name; + $ballotFile->path = $ObjectURL; + $ballotFile->url = $ObjectURL; $ballotFile->save(); } } + if ($request->file_ids_remove) { foreach($request->file_ids_remove as $file_id) { - BallotFile::where('id', $file_id)->where('ballot_id', $id)->delete(); + BallotFile::where('id', $file_id) + ->where('ballot_id', $id) + ->delete(); } } DB::commit(); return $this->metaSuccess(); } catch (\Exception $ex) { DB::rollBack(); - return $this->errorResponse('Submit ballot fail', Response::HTTP_BAD_REQUEST, $ex->getMessage()); + + return $this->errorResponse( + 'Submit ballot fail', + Response::HTTP_BAD_REQUEST, + $ex->getMessage() + ); } } public function getBallots(Request $request) { - $limit = $request->limit ?? 50; - $status = $request->status; - $sort_key = $request->sort_key ?? ''; - $sort_direction = $request->sort_direction ?? ''; - if (!$sort_key) $sort_key = 'ballot.id'; - if (!$sort_direction) $sort_direction = 'desc'; - - $now = Carbon::now('EST'); - $startDate = $now->format('Y-m-d'); - $startTime = $now->format('H:i:s'); - + $limit = $request->limit ?? 50; + $status = $request->status; + $sort_key = $request->sort_key ?? 'ballot.id'; + $sort_direction = $request->sort_direction ?? 'desc'; + $now = Carbon::now('EST'); + $startDate = $now->format('Y-m-d'); + $startTime = $now->format('H:i:s'); + if ($status == 'active') { $ballots = Ballot::with(['user', 'vote']) - ->where('ballot.status', 'active') - ->where(function ($query) use ($startDate, $startTime) { - $query->where('start_date', '<', $startDate) - ->orWhere(function ($query) use ($startDate, $startTime) { - $query->where('start_date', $startDate) - ->where('start_time', '<=', $startTime); - }); - }) - ->orderBy($sort_key, $sort_direction) - ->paginate($limit); + ->where('ballot.status', 'active') + ->where(function ($query) use ($startDate, $startTime) { + $query->where('start_date', '<', $startDate) + ->orWhere(function ($query) use ($startDate, $startTime) { + $query->where('start_date', $startDate) + ->where('start_time', '<=', $startTime); + }); + }) + ->orderBy($sort_key, $sort_direction) + ->paginate($limit); } else if ($status && $status == 'scheduled') { $ballots = Ballot::with(['user', 'vote']) - ->where('ballot.status', 'active') - ->where(function ($query) use ($startDate, $startTime) { - $query->where('start_date', '>', $startDate) - ->orWhere(function ($query) use ($startDate, $startTime) { - $query->where('start_date', $startDate) - ->where('start_time', '>', $startTime); - }); - }) - ->orderBy($sort_key, $sort_direction) - ->paginate($limit); + ->where('ballot.status', 'active') + ->where(function ($query) use ($startDate, $startTime) { + $query->where('start_date', '>', $startDate) + ->orWhere(function ($query) use ($startDate, $startTime) { + $query->where('start_date', $startDate) + ->where('start_time', '>', $startTime); + }); + }) + ->orderBy($sort_key, $sort_direction) + ->paginate($limit); } else if ($status && $status != 'active' && $status != 'scheduled') { $ballots = Ballot::with(['user', 'vote']) - ->where('ballot.status', '!=', 'active') - ->orderBy($sort_key, $sort_direction) - ->paginate($limit); + ->where('ballot.status', '!=', 'active') + ->orderBy($sort_key, $sort_direction) + ->paginate($limit); } else { - $ballots = Ballot::with(['user', 'vote'])->orderBy($sort_key, $sort_direction)->paginate($limit); + $ballots = Ballot::with(['user', 'vote']) + ->orderBy($sort_key, $sort_direction) + ->paginate($limit); } return $this->successResponse($ballots); } public function getDetailBallot($id) { - $ballot = Ballot::with(['user', 'vote', 'files'])->where('id', $id)->first(); + $ballot = Ballot::with(['user', 'vote', 'files']) + ->where('id', $id) + ->first(); + if (!$ballot) { - return $this->errorResponse('Not found ballot', Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + 'Not found ballot', + Response::HTTP_BAD_REQUEST + ); } + return $this->successResponse($ballot); } public function cancelBallot($id) { $ballot = Ballot::where('id', $id)->first(); + if (!$ballot || $ballot->status != 'active') { - return $this->errorResponse('Cannot cancle ballot', Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + 'Cannot cancle ballot', + Response::HTTP_BAD_REQUEST + ); } + $ballot->time_end = now(); - $ballot->status = 'cancelled'; + $ballot->status = 'cancelled'; $ballot->save(); return $this->metaSuccess(); } @@ -525,7 +971,10 @@ public function cancelBallot($id) public function getBallotVotes($id, Request $request) { $limit = $request->limit ?? 50; - $data = VoteResult::where('ballot_id', '=', $id)->with(['user', 'user.profile'])->orderBy('created_at', 'ASC')->paginate($limit); + $data = VoteResult::where('ballot_id', '=', $id) + ->with(['user', 'user.profile']) + ->orderBy('created_at', 'ASC') + ->paginate($limit); return $this->successResponse($data); } @@ -533,15 +982,20 @@ public function getBallotVotes($id, Request $request) public function getViewFileBallot(Request $request, $fileId) { $limit = $request->limit ?? 50; - $data = BallotFileView::where('ballot_file_id', '=', $fileId)->with(['user', 'user.profile'])->orderBy('created_at', 'ASC')->paginate($limit); + $data = BallotFileView::where('ballot_file_id', '=', $fileId) + ->with(['user', 'user.profile']) + ->orderBy('created_at', 'ASC') + ->paginate($limit); + return $this->successResponse($data); } // Get Global Settings public function getGlobalSettings() { - $items = Setting::get(); + $items = Setting::get(); $settings = []; + if ($items) { foreach ($items as $item) { $settings[$item->name] = $item->value; @@ -554,22 +1008,27 @@ public function getGlobalSettings() // Update Global Settings public function updateGlobalSettings(Request $request) { - $validator = Validator::make($request->all(), [ - 'quorum_rate_ballot' => 'required', - ]); - if ($validator->fails()) { - return $this->validateResponse($validator->errors()); - } $items = [ - 'quorum_rate_ballot' => $request->quorum_rate_ballot, + 'quorum_rate_ballot' => ($request->quorum_rate_ballot ?? ''), + 'uptime_warning' => ($request->uptime_warning ?? ''), + 'uptime_probation' => ($request->uptime_probation ?? ''), + 'uptime_correction_time' => ($request->uptime_correction_time ?? ''), + 'uptime_calc_size' => ($request->uptime_calc_size ?? ''), + 'voting_eras_to_vote' => ($request->voting_eras_to_vote ?? ''), + 'voting_eras_since_redmark' => ($request->voting_eras_since_redmark ?? ''), + 'redmarks_revoke' => ($request->redmarks_revoke ?? ''), + 'responsiveness_warning' => ($request->responsiveness_warning ?? ''), + 'responsiveness_probation' => ($request->responsiveness_probation ?? '') ]; + foreach ($items as $name => $value) { $setting = Setting::where('name', $name)->first(); + if ($setting) { $setting->value = $value; $setting->save(); } else { - $setting = new Setting(); + $setting = new Setting(); $setting->value = $value; $setting->save(); } @@ -580,57 +1039,72 @@ public function updateGlobalSettings(Request $request) public function getSubAdmins(Request $request) { - $limit = $request->limit ?? 50; - $admins = User::with(['permissions'])->where(['role' => 'sub-admin']) + $limit = $request->limit ?? 50; + $admins = User::with(['permissions']) + ->where(['role' => 'sub-admin']) ->orderBy('created_at', 'DESC') ->paginate($limit); return $this->successResponse($admins); } - + public function inviteSubAdmin(Request $request) { $validator = Validator::make($request->all(), [ 'email' => 'required|email', ]); + if ($validator->fails()) { return $this->validateResponse($validator->errors()); } $isExist = User::where(['email' => $request->email])->count() > 0; + if ($isExist) { - return $this->errorResponse('This email has already been used to invite another admin.', Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + 'This email has already been used to invite another admin.', + Response::HTTP_BAD_REQUEST + ); } - $code = Str::random(6); - // $url = $request->header('origin') ?? $request->root(); - $url = getenv('SITE_URL'); - $inviteUrl = $url . '/register-sub-admin?code=' . $code . '&email=' . urlencode($request->email); - - VerifyUser::where('email', $request->email)->where('type', VerifyUser::TYPE_INVITE_ADMIN)->delete(); - - $verify = new VerifyUser(); - $verify->email = $request->email; - $verify->type = VerifyUser::TYPE_INVITE_ADMIN; - $verify->code = $code; + $code = Str::random(6); + $url = getenv('SITE_URL'); + + $inviteUrl = ( + $url . + '/register-sub-admin?code=' . + $code . + '&email=' . + urlencode($request->email) + ); + + VerifyUser::where('email', $request->email) + ->where('type', VerifyUser::TYPE_INVITE_ADMIN) + ->delete(); + + $verify = new VerifyUser(); + $verify->email = $request->email; + $verify->type = VerifyUser::TYPE_INVITE_ADMIN; + $verify->code = $code; $verify->created_at = now(); $verify->save(); + $admin = User::create([ - 'first_name' => 'faker', - 'last_name' => 'faker', - 'email' => $request->email, - 'password' => '', - 'type' => '', + 'first_name' => 'faker', + 'last_name' => 'faker', + 'email' => $request->email, + 'password' => '', + 'type' => '', 'member_status' => 'invited', - 'role' => 'sub-admin' + 'role' => 'sub-admin' ]); $data = [ - ['name' => 'intake', 'is_permission' => 0, 'user_id' => $admin->id], - ['name' => 'users', 'is_permission' => 0, 'user_id' => $admin->id], + ['name' => 'intake', 'is_permission' => 0, 'user_id' => $admin->id], + ['name' => 'users', 'is_permission' => 0, 'user_id' => $admin->id], ['name' => 'ballots', 'is_permission' => 0, 'user_id' => $admin->id], - ['name' => 'perks', 'is_permission' => 0, 'user_id' => $admin->id], - ['name' => 'teams', 'is_permission' => 0, 'user_id' => $admin->id], + ['name' => 'perks', 'is_permission' => 0, 'user_id' => $admin->id], + ['name' => 'teams', 'is_permission' => 0, 'user_id' => $admin->id], ]; Permission::insert($data); @@ -641,30 +1115,46 @@ public function inviteSubAdmin(Request $request) public function changeSubAdminPermissions(Request $request, $id) { - $validator = Validator::make($request->all(), [ - 'intake' => 'nullable|in:0,1', - 'users' => 'nullable|in:0,1', + $validator = Validator::make($request->all(), [ + 'intake' => 'nullable|in:0,1', + 'users' => 'nullable|in:0,1', 'ballots' => 'nullable|in:0,1', - 'perks' => 'nullable|in:0,1', - 'teams' => 'nullable|in:0,1', + 'perks' => 'nullable|in:0,1', + 'teams' => 'nullable|in:0,1', ]); if ($validator->fails()) { return $this->validateResponse($validator->errors()); } + $admin = User::find($id); - if ($admin == null || $admin->role != 'sub-admin') { - return $this->errorResponse('There is no admin user with this email', Response::HTTP_BAD_REQUEST); + + if ( + $admin == null || + $admin->role != 'sub-admin' + ) { + return $this->errorResponse( + 'There is no admin user with this email', + Response::HTTP_BAD_REQUEST + ); } + if (isset($request->intake)) { - $permisstion = Permission::where('user_id', $id)->where('name', 'intake')->first(); + $permisstion = Permission::where('user_id', $id) + ->where('name', 'intake') + ->first(); + if ($permisstion) { $permisstion->is_permission = $request->intake; $permisstion->save(); } } + if (isset($request->users)) { - $permisstion = Permission::where('user_id', $id)->where('name', 'users')->first(); + $permisstion = Permission::where('user_id', $id) + ->where('name', 'users') + ->first(); + if ($permisstion) { $permisstion->is_permission = $request->users; $permisstion->save(); @@ -672,7 +1162,10 @@ public function changeSubAdminPermissions(Request $request, $id) } if (isset($request->ballots)) { - $permisstion = Permission::where('user_id', $id)->where('name', 'ballots')->first(); + $permisstion = Permission::where('user_id', $id) + ->where('name', 'ballots') + ->first(); + if ($permisstion) { $permisstion->is_permission = $request->ballots; $permisstion->save(); @@ -680,7 +1173,10 @@ public function changeSubAdminPermissions(Request $request, $id) } if (isset($request->perks)) { - $permisstion = Permission::where('user_id', $id)->where('name', 'perks')->first(); + $permisstion = Permission::where('user_id', $id) + ->where('name', 'perks') + ->first(); + if ($permisstion) { $permisstion->is_permission = $request->perks; $permisstion->save(); @@ -688,15 +1184,18 @@ public function changeSubAdminPermissions(Request $request, $id) } if (isset($request->teams)) { - $permisstion = Permission::where('user_id', $id)->where('name', 'teams')->first(); + $permisstion = Permission::where('user_id', $id) + ->where('name', 'teams') + ->first(); + if ($permisstion) { $permisstion->is_permission = $request->teams; $permisstion->save(); } else { - $permisstion = new Permission(); + $permisstion = new Permission(); $permisstion->is_permission = $request->teams; - $permisstion->user_id = $id; - $permisstion->name = 'teams'; + $permisstion->user_id = $id; + $permisstion->name = 'teams'; $permisstion->save(); } } @@ -707,20 +1206,36 @@ public function changeSubAdminPermissions(Request $request, $id) public function resendLink(Request $request, $id) { $admin = User::find($id); - if ($admin == null || $admin->role != 'sub-admin') - return $this->errorResponse('No admin to be send invite link', Response::HTTP_BAD_REQUEST); - - $code = Str::random(6); - // $url = $request->header('origin') ?? $request->root(); - $url = getenv('SITE_URL'); - $inviteUrl = $url . '/register-sub-admin?code=' . $code . '&email=' . urlencode($admin->email); - - VerifyUser::where('email', $admin->email)->where('type', VerifyUser::TYPE_INVITE_ADMIN)->delete(); - - $verify = new VerifyUser(); - $verify->email = $admin->email; - $verify->type = VerifyUser::TYPE_INVITE_ADMIN; - $verify->code = $code; + + if ( + $admin == null || + $admin->role != 'sub-admin' + ) { + return $this->errorResponse( + 'No admin to be send invite link', + Response::HTTP_BAD_REQUEST + ); + } + + $code = Str::random(6); + $url = getenv('SITE_URL'); + + $inviteUrl = ( + $url . + '/register-sub-admin?code=' . + $code . + '&email=' . + urlencode($admin->email) + ); + + VerifyUser::where('email', $admin->email) + ->where('type', VerifyUser::TYPE_INVITE_ADMIN) + ->delete(); + + $verify = new VerifyUser(); + $verify->email = $admin->email; + $verify->type = VerifyUser::TYPE_INVITE_ADMIN; + $verify->code = $code; $verify->created_at = now(); $verify->save(); @@ -732,20 +1247,35 @@ public function resendLink(Request $request, $id) public function resetSubAdminResetPassword(Request $request, $id) { $admin = User::find($id); - if ($admin == null || $admin->role != 'sub-admin') - return $this->errorResponse('No admin to be revoked', Response::HTTP_BAD_REQUEST); - - $code = Str::random(6); - // $url = $request->header('origin') ?? $request->root(); - $url = getenv('SITE_URL'); - $resetUrl = $url . '/update-password?code=' . $code . '&email=' . urlencode($admin->email); - - VerifyUser::where('email', $admin->email)->where('type', VerifyUser::TYPE_RESET_PASSWORD)->delete(); - - $verify = new VerifyUser(); - $verify->email = $admin->email; - $verify->type = VerifyUser::TYPE_RESET_PASSWORD; - $verify->code = $code; + + if ( + $admin == null || + $admin->role != 'sub-admin' + ) { + return $this->errorResponse( + 'No admin to be revoked', + Response::HTTP_BAD_REQUEST + ); + } + + $code = Str::random(6); + $url = getenv('SITE_URL'); + $resetUrl = ( + $url . + '/update-password?code=' . + $code . + '&email=' . + urlencode($admin->email) + ); + + VerifyUser::where('email', $admin->email) + ->where('type', VerifyUser::TYPE_RESET_PASSWORD) + ->delete(); + + $verify = new VerifyUser(); + $verify->email = $admin->email; + $verify->type = VerifyUser::TYPE_RESET_PASSWORD; + $verify->code = $code; $verify->created_at = now(); $verify->save(); @@ -757,11 +1287,19 @@ public function resetSubAdminResetPassword(Request $request, $id) public function revokeSubAdmin(Request $request, $id) { $admin = User::find($id); - if ($admin == null || $admin->role != 'sub-admin') - return $this->errorResponse('No admin to be revoked', Response::HTTP_BAD_REQUEST); + + if ( + $admin == null || + $admin->role != 'sub-admin' + ) { + return $this->errorResponse( + 'No admin to be revoked', + Response::HTTP_BAD_REQUEST + ); + } $admin->member_status = 'revoked'; - $admin->banned = 1; + $admin->banned = 1; $admin->save(); return $this->metaSuccess(); @@ -770,13 +1308,23 @@ public function revokeSubAdmin(Request $request, $id) public function undoRevokeSubAdmin(Request $request, $id) { $admin = User::find($id); - if ($admin == null || $admin->role != 'sub-admin') - return $this->errorResponse('No admin to be revoked', Response::HTTP_BAD_REQUEST); + + if ( + $admin == null || + $admin->role != 'sub-admin' + ) { + return $this->errorResponse( + 'No admin to be revoked', + Response::HTTP_BAD_REQUEST + ); + } + if ($admin->password) { $admin->member_status = 'active'; } else { $admin->member_status = 'invited'; } + $admin->banned = 0; $admin->save(); @@ -786,72 +1334,126 @@ public function undoRevokeSubAdmin(Request $request, $id) public function getIpHistories(Request $request, $id) { $admin = User::find($id); - if ($admin == null || $admin->role != 'sub-admin') { - return $this->errorResponse('Not found admin', Response::HTTP_BAD_REQUEST); + + if ( + $admin == null || + $admin->role != 'sub-admin' + ) { + return $this->errorResponse( + 'Not found admin', + Response::HTTP_BAD_REQUEST + ); } - $limit = $request->limit ?? 50; + + $limit = $request->limit ?? 50; $ipAddress = IpHistory::where(['user_id' => $admin->id]) ->orderBy('created_at', 'DESC') ->paginate($limit); return $this->successResponse($ipAddress); } - + public function approveIntakeUser($id) { $admin = auth()->user(); + $user = User::where('id', $id) + ->where('banned', 0) + ->where('role', 'member') + ->first(); - $user = User::where('id', $id)->where('banned', 0)->where('role', 'member')->first(); if ($user && $user->letter_file) { $user->letter_verified_at = now(); $user->save(); $emailerData = EmailerHelper::getEmailerData(); - EmailerHelper::triggerUserEmail($user->email, 'Your letter of motivation is APPROVED', $emailerData, $user); - if ($user->letter_verified_at && $user->node_verified_at) { - EmailerHelper::triggerUserEmail($user->email, 'Congratulations', $emailerData, $user); + + EmailerHelper::triggerUserEmail( + $user->email, + 'Your letter of motivation is APPROVED', + $emailerData, + $user + ); + + if ( + $user->letter_verified_at && + $user->node_verified_at + ) { + EmailerHelper::triggerUserEmail( + $user->email, + 'Congratulations', + $emailerData, + $user + ); } return $this->metaSuccess(); } - return $this->errorResponse('Fail approved User', Response::HTTP_BAD_REQUEST); + + return $this->errorResponse( + 'Fail approved User', + Response::HTTP_BAD_REQUEST + ); } public function resetIntakeUser($id, Request $request) { $admin = auth()->user(); + $user = User::where('id', $id) + ->where('banned', 0) + ->where('role', 'member') + ->first(); - $user = User::where('id', $id)->where('banned', 0)->where('role', 'member')->first(); if ($user) { $user->letter_verified_at = null; - $user->letter_file = null; + $user->letter_file = null; $user->letter_rejected_at = now(); $user->save(); $message = trim($request->get('message')); + if (!$message) { - return $this->errorResponse('please input message', Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + 'please input message', + Response::HTTP_BAD_REQUEST + ); } - Mail::to($user->email)->send(new AdminAlert('You need to submit letter again', $message)); + + Mail::to($user->email)->send(new AdminAlert( + 'You need to submit letter again', + $message + )); + return $this->metaSuccess(); } - return $this->errorResponse('Fail reset User', Response::HTTP_BAD_REQUEST); + + return $this->errorResponse( + 'Fail reset User', + Response::HTTP_BAD_REQUEST + ); } public function banUser($id) { $admin = auth()->user(); + $user = User::where('id', $id) + ->where('banned', 0) + ->first(); - $user = User::where('id', $id)->where('banned', 0)->first(); if ($user) { $user->banned = 1; $user->save(); return $this->metaSuccess(); } - return $this->errorResponse('Fail Ban User', Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + 'Fail Ban User', + Response::HTTP_BAD_REQUEST + ); } public function removeUser($id, Request $request) { - $user = User::where('id', $id)->where('role', 'member')->first(); + $user = User::where('id', $id) + ->where('role', 'member') + ->first(); + if ($user) { Shuftipro::where('user_id', $user->id)->delete(); ShuftiproTemp::where('user_id', $user->id)->delete(); @@ -859,80 +1461,107 @@ public function removeUser($id, Request $request) $user->delete(); return $this->metaSuccess(); } - return $this->errorResponse('Fail remove User', Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + 'Fail remove User', + Response::HTTP_BAD_REQUEST + ); } public function getVerificationUsers(Request $request) { $limit = $request->limit ?? 50; - $users = User::where('users.role', 'member')->where('banned', 0) - ->join('profile', function ($query) { - $query->on('profile.user_id', '=', 'users.id') - ->where('profile.status', 'pending'); - }) - ->join('shuftipro', 'shuftipro.user_id', '=', 'users.id') - ->select([ - 'users.id as user_id', - 'users.created_at', - 'users.email', - 'profile.*', - 'shuftipro.status as kyc_status', - 'shuftipro.background_checks_result', - 'shuftipro.manual_approved_at' - ])->paginate($limit); + $users = User::where('users.role', 'member') + ->where('banned', 0) + ->join('profile', function ($query) { + $query->on('profile.user_id', '=', 'users.id') + ->where('profile.status', 'pending'); + }) + ->join('shuftipro', 'shuftipro.user_id', '=', 'users.id') + ->select([ + 'users.id as user_id', + 'users.created_at', + 'users.email', + 'profile.*', + 'shuftipro.status as kyc_status', + 'shuftipro.background_checks_result', + 'shuftipro.manual_approved_at' + ])->paginate($limit); + return $this->successResponse($users); } // Reset KYC public function resetKYC($id, Request $request) { - $admin = auth()->user(); - + $admin = auth()->user(); $message = trim($request->get('message')); + if (!$message) { - return $this->errorResponse('please input message', Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + 'please input message', + Response::HTTP_BAD_REQUEST + ); } - $user = User::with(['profile'])->where('id', $id)->first(); + $user = User::with(['profile']) + ->where('id', $id) + ->first(); + if ($user && $user->profile) { $user->profile->status = null; $user->profile->save(); - + Profile::where('user_id', $user->id)->delete(); Shuftipro::where('user_id', $user->id)->delete(); ShuftiproTemp::where('user_id', $user->id)->delete(); DocumentFile::where('user_id', $user->id)->delete(); $user->kyc_verified_at = null; - $user->approve_at = null; - $user->reset_kyc = 1; + $user->approve_at = null; + $user->reset_kyc = 1; $user->save(); - - Mail::to($user->email)->send(new AdminAlert('You need to submit KYC again', $message)); + + Mail::to($user->email)->send(new AdminAlert( + 'You need to submit KYC again', + $message + )); + return $this->metaSuccess(); } - return $this->errorResponse('Fail Reset KYC', Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + 'Fail Reset KYC', + Response::HTTP_BAD_REQUEST + ); } public function refreshLinks($id) { - $url = 'https://api.shuftipro.com/status'; - $user = User::with('shuftipro')->find($id); - + $url = 'https://api.shuftipro.com/status'; + $user = User::with('shuftipro')->find($id); $shuftipro = $user->shuftipro; + if ($shuftipro) { - $client_id = config('services.shufti.client_id'); + $client_id = config('services.shufti.client_id'); $secret_key = config('services.shufti.client_secret'); - $response = Http::withBasicAuth($client_id, $secret_key)->post($url, [ + $response = Http::withBasicAuth( + $client_id, + $secret_key + )->post($url, [ 'reference' => $shuftipro->reference_id ]); $data = $response->json(); - if (!$data || !is_array($data)) return; - if (!isset($data['reference']) || !isset($data['event'])) { + if (!$data || !is_array($data)) { + return; + } + + if ( + !isset($data['reference']) || + !isset($data['event']) + ) { return $this->successResponse([ 'success' => false, ]); @@ -952,12 +1581,20 @@ public function refreshLinks($id) $proofs = isset($data['proofs']) ? $data['proofs'] : null; - if ($proofs && isset($proofs['document']) && isset($proofs['document']['proof'])) { + if ( + $proofs && + isset($proofs['document']) && + isset($proofs['document']['proof']) + ) { $shuftipro->document_proof = $proofs['document']['proof']; } - + // Address Proof - if ($proofs && isset($proofs['address']) && isset($proofs['address']['proof'])) { + if ( + $proofs && + isset($proofs['address']) && + isset($proofs['address']['proof']) + ) { $shuftipro->address_proof = $proofs['address']['proof']; } @@ -976,10 +1613,10 @@ public function refreshLinks($id) public function banAndDenyUser($id) { $user = User::with(['shuftipro', 'profile']) - ->where('id', $id) - ->where('users.role', 'member') - ->where('banned', 0) - ->first(); + ->where('id', $id) + ->where('users.role', 'member') + ->where('banned', 0) + ->first(); if ($user && $user->profileT) { $user->profile->status = 'denied'; @@ -989,41 +1626,51 @@ public function banAndDenyUser($id) return $this->metaSuccess(); } - return $this->errorResponse('Fail deny and ban user', Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + 'Fail deny and ban user', + Response::HTTP_BAD_REQUEST + ); } public function getVerificationDetail($id) { $user = User::with(['shuftipro', 'profile', 'documentFiles']) - ->leftJoin('shuftipro', 'shuftipro.user_id', '=', 'users.id') - ->where('users.id', $id) - ->select([ - 'users.*', - 'shuftipro.status as kyc_status', - 'shuftipro.background_checks_result', - ]) - ->where('users.role', 'member') - ->where('banned', 0) - ->first(); + ->leftJoin('shuftipro', 'shuftipro.user_id', '=', 'users.id') + ->where('users.id', $id) + ->select([ + 'users.*', + 'shuftipro.status as kyc_status', + 'shuftipro.background_checks_result', + ]) + ->where('users.role', 'member') + ->where('banned', 0) + ->first(); if ($user) { - if (isset($user->shuftipro) && isset($user->shuftipro->address_proof) && $user->shuftipro->address_proof) { + if ( + isset($user->shuftipro) && + isset($user->shuftipro->address_proof) && + $user->shuftipro->address_proof + ) { $url = Storage::disk('local')->url($user->shuftipro->address_proof); $user->shuftipro->address_proof_link = asset($url); } return $this->successResponse($user); } - return $this->errorResponse('Fail get verification user', Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + 'Fail get verification user', + Response::HTTP_BAD_REQUEST + ); } public function approveDocument($id) { $user = User::with(['profile']) - ->where('id', $id) - ->where('users.role', 'member') - ->where('banned', 0) - ->first(); + ->where('id', $id) + ->where('users.role', 'member') + ->where('banned', 0) + ->first(); if ($user && $user->profile) { $user->profile->document_verified_at = now(); @@ -1031,17 +1678,20 @@ public function approveDocument($id) return $this->metaSuccess(); } - return $this->errorResponse('Fail approve document', Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + 'Fail approve document', + Response::HTTP_BAD_REQUEST + ); } public function activeUser($id) { $user = User::with(['profile']) - ->where('id', $id) - ->where('users.role', 'member') - ->where('banned', 0) - ->first(); - + ->where('id', $id) + ->where('users.role', 'member') + ->where('banned', 0) + ->first(); + if ($user && $user->profile) { $user->profile->status = 'approved'; $user->profile->save(); @@ -1050,15 +1700,18 @@ public function activeUser($id) return $this->metaSuccess(); } - return $this->errorResponse('Fail active document', Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + 'Fail active document', + Response::HTTP_BAD_REQUEST + ); } // Add Emailer Admin public function addEmailerAdmin(Request $request) { - $user = Auth::user(); - + $user = Auth::user(); $email = $request->get('email'); + if (!$email) { return [ 'success' => false, @@ -1067,6 +1720,7 @@ public function addEmailerAdmin(Request $request) } $record = EmailerAdmin::where('email', $email)->first(); + if ($record) { return [ 'success' => false, @@ -1074,7 +1728,7 @@ public function addEmailerAdmin(Request $request) ]; } - $record = new EmailerAdmin; + $record = new EmailerAdmin; $record->email = $email; $record->save(); @@ -1092,33 +1746,40 @@ public function deleteEmailerAdmin($adminId, Request $request) // Get Emailer Data public function getEmailerData(Request $request) { - $user = Auth::user(); - $data = []; + $user = Auth::user(); + $data = []; + $admins = EmailerAdmin::where('id', '>', 0) + ->orderBy('email', 'asc') + ->get(); + + $triggerAdmin = EmailerTriggerAdmin::where('id', '>', 0) + ->orderBy('id', 'asc') + ->get(); - $admins = EmailerAdmin::where('id', '>', 0)->orderBy('email', 'asc')->get(); - $triggerAdmin = EmailerTriggerAdmin::where('id', '>', 0)->orderBy('id', 'asc')->get(); - $triggerUser = EmailerTriggerUser::where('id', '>', 0)->orderBy('id', 'asc')->get(); + $triggerUser = EmailerTriggerUser::where('id', '>', 0) + ->orderBy('id', 'asc') + ->get(); $data = [ - 'admins' => $admins, + 'admins' => $admins, 'triggerAdmin' => $triggerAdmin, - 'triggerUser' => $triggerUser, + 'triggerUser' => $triggerUser, ]; return [ 'success' => true, - 'data' => $data + 'data' => $data ]; } // Update Emailer Trigger Admin public function updateEmailerTriggerAdmin($recordId, Request $request) { - $user = Auth::user(); + $user = Auth::user(); $record = EmailerTriggerAdmin::find($recordId); if ($record) { - $enabled = (int) $request->get('enabled'); + $enabled = (int)$request->get('enabled'); $record->enabled = $enabled; $record->save(); return ['success' => true]; @@ -1130,15 +1791,17 @@ public function updateEmailerTriggerAdmin($recordId, Request $request) // Update Emailer Trigger User public function updateEmailerTriggerUser($recordId, Request $request) { - $user = Auth::user(); + $user = Auth::user(); $record = EmailerTriggerUser::find($recordId); if ($record) { - $enabled = (int) $request->get('enabled'); - $content = $request->get('content'); - + $enabled = (int)$request->get('enabled'); + $content = $request->get('content'); $record->enabled = $enabled; - if ($content) $record->content = $content; + + if ($content) { + $record->content = $content; + } $record->save(); @@ -1162,25 +1825,18 @@ public function updateMonitoringCriteria($type, Request $request) $validator = Validator::make($request->all(), [ 'warning_level' => 'required|integer', 'probation_start' => 'required', - // 'frame_calculate_unit' => 'required|in:Weeks,Days,Hours', - // 'frame_calculate_value' => 'required|integer', 'given_to_correct_unit' => 'required|in:Weeks,Days,Hours', 'given_to_correct_value' => 'required|integer', - // 'system_check_unit' => 'required|in:Weeks,Days,Hours', - // 'system_check_value' => 'required|integer', ]); - if ($validator->fails()) + if ($validator->fails()) { return $this->validateResponse($validator->errors()); + } - $record->warning_level = $request->warning_level; - $record->probation_start = $request->probation_start; - // $record->frame_calculate_unit = $request->frame_calculate_unit; - // $record->frame_calculate_value = $request->frame_calculate_value; - $record->given_to_correct_unit = $request->given_to_correct_unit; + $record->warning_level = $request->warning_level; + $record->probation_start = $request->probation_start; + $record->given_to_correct_unit = $request->given_to_correct_unit; $record->given_to_correct_value = $request->given_to_correct_value; - // $record->system_check_unit = $request->system_check_unit; - // $record->system_check_value = $request->system_check_value; $record->save(); return ['success' => true]; @@ -1195,10 +1851,11 @@ public function updateLockRules(Request $request, $id) 'is_lock' => 'required|boolean' ]); - if ($validator->fails()) return $this->validateResponse($validator->errors()); - - $rule = LockRules::where('id', $id)->first(); + if ($validator->fails()) { + return $this->validateResponse($validator->errors()); + } + $rule = LockRules::where('id', $id)->first(); $rule->is_lock = $request->is_lock; $rule->save(); @@ -1208,19 +1865,26 @@ public function updateLockRules(Request $request, $id) public function getLockRules() { $ruleKycNotVerify = LockRules::where('type', 'kyc_not_verify') - ->orderBy('id', 'ASC')->select(['id', 'screen', 'is_lock'])->get(); + ->orderBy('id', 'ASC') + ->select(['id', 'screen', 'is_lock']) + ->get(); + $ruleStatusIsPoor = LockRules::where('type', 'status_is_poor') - ->orderBy('id', 'ASC')->select(['id', 'screen', 'is_lock'])->get(); + ->orderBy('id', 'ASC') + ->select(['id', 'screen', 'is_lock']) + ->get(); + $data = [ 'kyc_not_verify' => $ruleKycNotVerify, 'status_is_poor' => $ruleStatusIsPoor, ]; + return $this->successResponse($data); } public function getListNodes(Request $request) { - $limit = $request->limit ?? 50; + $limit = $request->limit ?? 50; $node_failing = $request->node_failing ?? ''; $nodes = UserAddress::select([ @@ -1244,15 +1908,21 @@ public function getListNodes(Request $request) // Get GraphInfo public function getGraphInfo(Request $request) { - $user = Auth::user(); - $graphDataDay = $graphDataWeek = $graphDataMonth = $graphDataYear = []; - - $timeDay = Carbon::now('UTC')->subHours(24); - $timeWeek = Carbon::now('UTC')->subDays(7); + $user = Auth::user(); + $graphDataDay = + $graphDataWeek = + $graphDataMonth = + $graphDataYear = []; + + $timeDay = Carbon::now('UTC')->subHours(24); + $timeWeek = Carbon::now('UTC')->subDays(7); $timeMonth = Carbon::now('UTC')->subDays(30); - $timeYear = Carbon::now('UTC')->subYear(); + $timeYear = Carbon::now('UTC')->subYear(); + + $items = TokenPrice::orderBy('created_at', 'desc') + ->where('created_at', '>=', $timeDay) + ->get(); - $items = TokenPrice::orderBy('created_at', 'desc')->where('created_at', '>=', $timeDay)->get(); if ($items && count($items)) { foreach ($items as $item) { $name = strtotime($item->created_at); @@ -1260,7 +1930,10 @@ public function getGraphInfo(Request $request) } } - $items = TokenPrice::orderBy('created_at', 'desc')->where('created_at', '>=', $timeWeek)->get(); + $items = TokenPrice::orderBy('created_at', 'desc') + ->where('created_at', '>=', $timeWeek) + ->get(); + if ($items && count($items)) { foreach ($items as $item) { $name = strtotime($item->created_at); @@ -1268,7 +1941,10 @@ public function getGraphInfo(Request $request) } } - $items = TokenPrice::orderBy('created_at', 'desc')->where('created_at', '>=', $timeMonth)->get(); + $items = TokenPrice::orderBy('created_at', 'desc') + ->where('created_at', '>=', $timeMonth) + ->get(); + if ($items && count($items)) { foreach ($items as $item) { $name = strtotime($item->created_at); @@ -1276,7 +1952,10 @@ public function getGraphInfo(Request $request) } } - $items = TokenPrice::orderBy('created_at', 'desc')->where('created_at', '>=', $timeYear)->get(); + $items = TokenPrice::orderBy('created_at', 'desc') + ->where('created_at', '>=', $timeYear) + ->get(); + if ($items && count($items)) { foreach ($items as $item) { $name = strtotime($item->created_at); @@ -1285,10 +1964,10 @@ public function getGraphInfo(Request $request) } return $this->successResponse([ - 'day' => $graphDataDay, - 'week' => $graphDataWeek, + 'day' => $graphDataDay, + 'week' => $graphDataWeek, 'month' => $graphDataMonth, - 'year' => $graphDataYear, + 'year' => $graphDataYear, ]); } @@ -1300,46 +1979,48 @@ public function uploadMembershipFile(Request $request) 'file' => 'required|mimes:pdf,docx,doc,txt,rtf|max:5000', ]); - if ($validator->fails()) + if ($validator->fails()) { return $this->validateResponse($validator->errors()); + } $filenameWithExt = $request->file('file')->getClientOriginalName(); - //Get just filename - $filename = pathinfo($filenameWithExt, PATHINFO_FILENAME); - // Get just ext - $extension = $request->file('file')->getClientOriginalExtension(); - // new filename hash - $filenamehash = md5(Str::random(10) . '_' . (string)time()); - // Filename to store + $filename = pathinfo($filenameWithExt, PATHINFO_FILENAME); + $extension = $request->file('file')->getClientOriginalExtension(); + $filenamehash = md5(Str::random(10) . '_' . (string)time()); $fileNameToStore = $filenamehash . '.' . $extension; // S3 File Upload $S3 = new S3Client([ - 'version' => 'latest', - 'region' => getenv('AWS_DEFAULT_REGION'), + 'version' => 'latest', + 'region' => getenv('AWS_DEFAULT_REGION'), 'credentials' => [ - 'key' => getenv('AWS_ACCESS_KEY_ID'), - 'secret' => getenv('AWS_SECRET_ACCESS_KEY'), + 'key' => getenv('AWS_ACCESS_KEY_ID'), + 'secret' => getenv('AWS_SECRET_ACCESS_KEY'), ], ]); $s3result = $S3->putObject([ - 'Bucket' => getenv('AWS_BUCKET'), - 'Key' => 'client_uploads/' . $fileNameToStore, + 'Bucket' => getenv('AWS_BUCKET'), + 'Key' => 'client_uploads/' . $fileNameToStore, 'SourceFile' => $request->file('file') ]); $ObjectURL = $s3result['ObjectURL'] ?? getenv('SITE_URL') . '/not-found'; MembershipAgreementFile::where('id', '>', 0)->delete(); - $membershipAgreementFile = new MembershipAgreementFile(); + $membershipAgreementFile = new MembershipAgreementFile(); $membershipAgreementFile->name = $filenameWithExt; $membershipAgreementFile->path = $ObjectURL; - $membershipAgreementFile->url = $ObjectURL; + $membershipAgreementFile->url = $ObjectURL; $membershipAgreementFile->save(); DB::table('users')->update(['membership_agreement' => 0]); + return $this->successResponse($membershipAgreementFile); } catch (\Exception $ex) { - return $this->errorResponse(__('Failed upload file'), Response::HTTP_BAD_REQUEST, $ex->getMessage()); + return $this->errorResponse( + __('Failed upload file'), + Response::HTTP_BAD_REQUEST, + $ex->getMessage() + ); } } diff --git a/app/Http/Controllers/Api/V1/MetricController.php b/app/Http/Controllers/Api/V1/MetricController.php index 709f3aef..b9b35804 100644 --- a/app/Http/Controllers/Api/V1/MetricController.php +++ b/app/Http/Controllers/Api/V1/MetricController.php @@ -23,13 +23,77 @@ class MetricController extends Controller { public function getMetric(Request $request) { - $user = auth()->user(); + $return_object = array(); + $user = auth()->user(); + $user_id = $user->id; $public_address_node = $request->get('public_address_node'); if (!$public_address_node) { $public_address_node = $user->public_address_node; } + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + $addresses = DB::select(" + SELECT + a.public_key, + a.historical_performance AS uptime, + a.bid_delegators_count AS delegators, + a.port8888_peers AS peers, + a.bid_self_staked_amount, a.bid_total_staked_amount, + a.bad_mark, a.stable, a.in_auction + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.era_id = $current_era_id + AND ( + b.user_id = $user_id OR + a.public_key = '$public_address_node' + ) + "); + $return_object['addresses'] = $addresses; + + foreach ($addresses as &$address) { + $a = $address->public_key; + + $eras_sinse_bad_mark = DB::select(" + SELECT a.era_id + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.public_key = '$a' + AND a.bad_mark = 1 + ORDER BY era_id DESC + LIMIT 1 + "); + $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; + $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; + $address->eras_sinse_bad_mark = $eras_sinse_bad_mark; + } + + $monitoring_criteria = DB::select(" + SELECT * + FROM monitoring_criteria + "); + $return_object['monitoring_criteria'] = $monitoring_criteria; + info($return_object); + return $this->successResponse($return_object); + //// done + + + + + + + + + $isTotal = (int) $request->get('isTotal'); $max_update_responsiveness = DB::select(" @@ -189,6 +253,7 @@ public function getMetric(Request $request) $metric->stake_amount = $stake_amount; $metric->self_stake_amount = $self_stake_amount; } + // info($metric); return $this->successResponse($metric); } @@ -198,21 +263,53 @@ public function updateMetric(Request $request, $id) $validator = Validator::make($request->all(), [ 'uptime' => 'nullable|numeric|between:0,100', ]); - if ($validator->fails()) return $this->validateResponse($validator->errors()); + + if ($validator->fails()) { + return $this->validateResponse($validator->errors()); + } $user = User::where('id', $id)->where('role', 'member')->first(); - if (!$user) return $this->errorResponse('User not found', Response::HTTP_BAD_REQUEST); - + + if (!$user) { + return $this->errorResponse( + 'User not found', + Response::HTTP_BAD_REQUEST + ); + } + $metric = Metric::where('user_id', $id)->first(); - if (!$metric) $metric = new Metric(); - if (isset($request->uptime) && $request->uptime != null) + + if (!$metric) { + $metric = new Metric(); + } + + if ( + isset($request->uptime) && $request->uptime != null + ) { $metric->uptime = $request->uptime; - if (isset($request->block_height_average) && $request->block_height_average != null) + } + + if ( + isset($request->block_height_average) && + $request->block_height_average != null + ) { $metric->block_height_average = $request->block_height_average; - if (isset($request->update_responsiveness) && $request->update_responsiveness != null) + } + + if ( + isset($request->update_responsiveness) && + $request->update_responsiveness != null + ) { $metric->update_responsiveness = $request->update_responsiveness; - if (isset($request->peers) && $request->peers != null) + } + + if ( + isset($request->peers) && + $request->peers != null + ) { $metric->peers = $request->peers; + } + $metric->user_id = $id; $metric->save(); return $this->successResponse($metric); @@ -221,7 +318,68 @@ public function updateMetric(Request $request, $id) public function getMetricUser($id) { $user = User::find($id); - if (!$user) return $this->successResponse([]); + + if (!$user) { + return $this->successResponse([]); + } + + $return_object = array(); + $user_id = $user->id; + + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + $addresses = DB::select(" + SELECT + a.public_key, + a.historical_performance AS uptime, + a.bid_delegators_count AS delegators, + a.port8888_peers AS peers, + a.bid_self_staked_amount, a.bid_total_staked_amount, + a.bad_mark, a.stable, a.in_auction + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.era_id = $current_era_id + AND b.user_id = $user_id + "); + $return_object['addresses'] = $addresses; + + foreach ($addresses as &$address) { + $a = $address->public_key; + + $eras_sinse_bad_mark = DB::select(" + SELECT a.era_id + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.public_key = '$a' + AND a.bad_mark = 1 + ORDER BY era_id DESC + LIMIT 1 + "); + $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; + $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; + $address->eras_sinse_bad_mark = $eras_sinse_bad_mark; + } + + $monitoring_criteria = DB::select(" + SELECT * + FROM monitoring_criteria + "); + $return_object['monitoring_criteria'] = $monitoring_criteria; + info($return_object); + return $this->successResponse($return_object); + //// done + + + + $max_update_responsiveness = DB::select("SELECT max(update_responsiveness) as max_update_responsiveness FROM ( @@ -271,7 +429,7 @@ public function getMetricUser($id) $nodeInfo = NodeInfo::where('node_address', strtolower($user->public_address_node))->first(); if (!$nodeInfo) $nodeInfo = new NodeInfo(); - + $latest_uptime = $nodeInfo->uptime ?? null; $nodeInfo_uptime = $nodeInfo->uptime ?? null; $nodeInfo_block_height = $nodeInfo->block_height ?? null; @@ -295,7 +453,7 @@ public function getMetricUser($id) $monitoringCriteria = MonitoringCriteria::get(); $nodeInfo = NodeInfo::where('node_address', strtolower($user->public_address_node))->first(); - + $rank = $user->rank; $totalCount = User::select([ @@ -308,7 +466,7 @@ public function getMetricUser($id) ->whereNotNull('public_address_node') ->get() ->count(); - + $delegators = $stake_amount = $self_stake_amount = 0; if ($nodeInfo) { $delegators = $nodeInfo->delegators_count; @@ -330,7 +488,66 @@ public function getMetricUser($id) public function getMetricUserByNodeName($node) { - $node = strtolower($node); + $node = strtolower($node); + $return_object = array(); + + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + $addresses = DB::select(" + SELECT + a.public_key, + a.historical_performance AS uptime, + a.bid_delegators_count AS delegators, + a.port8888_peers AS peers, + a.bid_self_staked_amount, a.bid_total_staked_amount, + a.bad_mark, a.stable, + c.node_status + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + JOIN users AS c + ON b.user_id = c.id + WHERE a.era_id = $current_era_id + AND b.public_address_node = '$node' + "); + $return_object['addresses'] = $addresses; + + foreach ($addresses as &$address) { + $a = $address->public_key; + + $eras_sinse_bad_mark = DB::select(" + SELECT a.era_id + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.public_key = '$a' + AND a.bad_mark = 1 + ORDER BY era_id DESC + LIMIT 1 + "); + $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; + $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; + $address->eras_sinse_bad_mark = $eras_sinse_bad_mark; + } + + $monitoring_criteria = DB::select(" + SELECT * + FROM monitoring_criteria + "); + $return_object['monitoring_criteria'] = $monitoring_criteria; + info($return_object); + //// done + + + + + $userAddress = UserAddress::with('user') ->has('user') ->where('public_address_node', $node) @@ -376,7 +593,7 @@ public function getMetricUserByNodeName($node) $latest_block_height = $latest->block_height ?? null; $latest_update_responsiveness = $latest->update_responsiveness ?? null; $latest_peers = $latest->peers ?? null; - + $metric = Metric::where('user_id', $user->id)->first(); if (!$metric) $metric = new Metric(); $metric_uptime = $metric->uptime ?? null; @@ -397,12 +614,12 @@ public function getMetricUserByNodeName($node) $metric->avg_block_height_average = $nodeInfo_block_height ?? $metric_block_height; $metric->avg_update_responsiveness = $nodeInfo_update_responsiveness ?? $metric_update_responsiveness; $metric->avg_peers = $nodeInfo_peers ?? $metric_peers; - + $metric->max_peers = $max_peers; $metric->max_update_responsiveness = $max_update_responsiveness; $metric->max_block_height_average = $max_block_height; $metric->max_uptime = $max_uptime; - + $metric->peers = $latest_peers ?? $metric_peers; $metric->update_responsiveness = $latest_update_responsiveness ?? $metric_update_responsiveness; $metric->block_height_average = $latest_block_height ?? $metric_block_height; diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 1eac8dc2..c1622f5c 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -77,21 +77,796 @@ class UserController extends Controller private $verifyUserRepo; private $profileRepo; private $ownerNodeRepo; - public $failed_verification_response; + public $failed_verification_response; public function __construct( - UserRepository $userRepo, + UserRepository $userRepo, VerifyUserRepository $verifyUserRepo, - ProfileRepository $profileRepo, - OwnerNodeRepository $ownerNodeRepo + ProfileRepository $profileRepo, + OwnerNodeRepository $ownerNodeRepo ) { - $this->userRepo = $userRepo; - $this->verifyUserRepo = $verifyUserRepo; - $this->profileRepo = $profileRepo; - $this->ownerNodeRepo = $ownerNodeRepo; + $this->userRepo = $userRepo; + $this->verifyUserRepo = $verifyUserRepo; + $this->profileRepo = $profileRepo; + $this->ownerNodeRepo = $ownerNodeRepo; $this->failed_verification_response = 'Failed verification'; } + /** + * + * Get all data required to populate user dashboard + * + * 1. Rank out of Total + * 2. Total stake across all user's nodes + * 3. Total self stake across all user's nodes + * 4. Total delegators across all user's nodes + * 5. New avg uptime for all user's node + * 6. ERAs active for oldest user's node + * 7. ERAs sinse bad mark across all user's nodes + * 8. Total bad marks across all user's nodes + * 9. Update Responsiveness for main node + * 10. Total peers across all nodes + * 11. Association members, verified/total counts + * + */ + public function getUserDashboard() { + $user = auth()->user(); + $user_id = $user->id ?? 0; + + // Get current era + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + // Define complete return object + $return = array( + "node_rank" => 0, + "node_rank_total" => 100, + "total_stake" => 0, + "total_self_stake" => 0, + "total_delegators" => 0, + "uptime" => 0, + "eras_active" => 0, + "eras_sinse_bad_mark" => $current_era_id, + "total_bad_marks" => 0, + "update_responsiveness" => 100, + "peers" => 0, + "total_members" => 0, + "verified_members" => 0, + "association_members" => array(), + "ranking" => array() + ); + + // get all active members + $association_members = DB::select(" + SELECT + a.public_key, + c.id, c.pseudonym, c.node_status, + d.status, d.extra_status + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + JOIN users AS c + ON b.user_id = c.id + JOIN profile AS d + ON c.id = d.user_id + WHERE a.era_id = $current_era_id + "); + + if (!$association_members) { + $association_members = array(); + } + + foreach ($association_members as $member) { + if ($member->status == 'approved') { + $return["association_members"][] = $member; + } + } + + // get verified members count + $verified_members = DB::select(" + SELECT a.pseudonym, b.status + FROM users AS a + JOIN profile AS b + ON a.id = b.user_id + WHERE b.status = 'approved' + "); + $verified_members = $verified_members ? count($verified_members) : 0; + $return["verified_members"] = $verified_members; + + // get total members count + $total_members = DB::select(" + SELECT pseudonym + FROM users + WHERE role = 'member' + "); + $total_members = $total_members ? count($total_members) : 0; + $return["total_members"] = $total_members; + + // find rank + $ranking = DB::select(" + SELECT + public_key, + historical_performance AS uptime, + bid_delegators_count, + bid_delegation_rate, + bid_total_staked_amount + FROM all_node_data + WHERE era_id = $current_era_id + AND in_curent_era = 1 + AND in_next_era = 1 + AND in_auction = 1 + AND bad_mark = 0 + "); + $max_delegators = 0; + $max_stake_amount = 0; + + foreach ($ranking as $r) { + if ((int)$r->bid_delegators_count > $max_delegators) { + $max_delegators = (int)$r->bid_delegators_count; + } + + if ((int)$r->bid_total_staked_amount > $max_stake_amount) { + $max_stake_amount = (int)$r->bid_total_staked_amount; + } + } + + foreach ($ranking as $r) { + $uptime_score = ( + 25 * (float)$r->uptime + ) / 100; + $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; + + $fee_score = ( + 25 * + (1 - ((float)$r->bid_delegation_rate / 100)) + ); + $fee_score = $fee_score < 0 ? 0 : $fee_score; + + $count_score = ( + (float)$r->bid_delegators_count / + $max_delegators + ) * 25; + $count_score = $count_score < 0 ? 0 : $count_score; + + $stake_score = ( + (float)$r->bid_total_staked_amount / + $max_stake_amount + ) * 25; + $stake_score = $stake_score < 0 ? 0 : $stake_score; + + $return["ranking"][$r->public_key] = ( + $uptime_score + + $fee_score + + $count_score + + $stake_score + ); + } + + uasort( + $return["ranking"], + function($x, $y) { + if ($x == $y) { + return 0; + } + + return ($x > $y) ? -1 : 1; + } + ); + + $sorted_ranking = array(); + $i = 1; + + foreach ($return["ranking"] as $public_key => $score) { + $sorted_ranking[$public_key] = $i; + $i += 1; + } + + $return["ranking"] = $sorted_ranking; + + // parse node addresses + $addresses = DB::select(" + SELECT + a.public_key, + a.historical_performance AS uptime, + a.bid_delegators_count AS delegators, + a.port8888_peers AS peers, + a.bid_self_staked_amount, a.bid_total_staked_amount, + a.bad_mark, a.stable + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + JOIN users AS c + ON b.user_id = c.id + WHERE a.era_id = $current_era_id + AND b.user_id = $user_id + "); + + if (!$addresses) { + $addresses = array(); + } + + // for each address belonging to a user + foreach ($addresses as $address) { + $a = $address->public_key ?? ''; + + $eras_sinse_bad_mark = DB::select(" + SELECT a.era_id + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.public_key = '$a' + AND a.bad_mark = 1 + ORDER BY era_id DESC + LIMIT 1 + "); + $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; + $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; + + if ($eras_sinse_bad_mark < $return["eras_sinse_bad_mark"]) { + $return["eras_sinse_bad_mark"] = $eras_sinse_bad_mark; + } + + $total_bad_marks = DB::select(" + SELECT bad_mark + FROM all_node_data + WHERE bad_mark = 1 + AND public_key = '$a' + "); + + $eras_active = DB::select(" + SELECT era_id + FROM all_node_data + WHERE public_key = '$a' + ORDER BY era_id ASC + LIMIT 1 + "); + + $eras_active = (int)($eras_active[0]->era_id ?? 0); + + if ($current_era_id - $eras_active > $return["eras_active"]) { + $return["eras_active"] = $current_era_id - $eras_active; + } + + if ( + array_key_exists($a, $return["ranking"]) && ( + $return["node_rank"] == 0 || + $return["ranking"][$a] < $return["node_rank"] + ) + ) { + $return["node_rank"] = $return["ranking"][$a]; + } + + $return["total_bad_marks"] += (int)(count($total_bad_marks ?? array())); + $return["total_stake"] += (int)($address->bid_total_staked_amount ?? 0); + $return["total_self_stake"] += (int)($address->bid_self_staked_amount ?? 0); + $return["total_delegators"] += (int)($address->delegators ?? 0); + $return["peers"] += (int)($address->peers ?? 0); + $return["uptime"] += (float)($address->uptime ?? 0); + } + + $addresses_count = count($addresses); + $addresses_count = $addresses_count ? $addresses_count : 1; + $return["uptime"] = $return["uptime"] / $addresses_count; + + // remove ranking object. not needed + unset($return["ranking"]); + + info($return); + return $this->successResponse($return); + } + + public function getMembershipPage() { + $user = auth()->user(); + $user_id = $user->id ?? 0; + + // Get current era + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + // Define complete return object + $return = array( + "node_status" => "Offline", + "kyc_status" => "Not Verified", + "uptime" => array(), + "avg_uptime" => 0, + "total_eras" => 0, + "eras_sinse_bad_mark" => $current_era_id, + "total_bad_marks" => 0, + "update_responsiveness" => 100, + "peers" => 0 + ); + + $addresses = DB::select(" + SELECT + a.public_key, + a.historical_performance AS uptime, + a.port8888_peers AS peers, + a.bid_inactive, a.bad_mark, a.stable, + c.status AS kyc_status + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + LEFT JOIN shuftipro AS c + ON b.user_id = c.user_id + WHERE a.era_id = $current_era_id + AND b.user_id = $user_id + "); + + if (!$addresses) { + $addresses = array(); + } + + if ( + isset($addresses[0]) && + $addresses[0]->kyc_status == 'approved' + ) { + $return["kyc_status"] = "Verified"; + } + + foreach ($addresses as $address) { + $a = $address->public_key ?? ''; + + $eras_sinse_bad_mark = DB::select(" + SELECT a.era_id + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.public_key = '$a' + AND a.bad_mark = 1 + ORDER BY era_id DESC + LIMIT 1 + "); + $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; + $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; + + if ($eras_sinse_bad_mark < $return["eras_sinse_bad_mark"]) { + $return["eras_sinse_bad_mark"] = $eras_sinse_bad_mark; + } + + $total_bad_marks = DB::select(" + SELECT bad_mark + FROM all_node_data + WHERE bad_mark = 1 + AND public_key = '$a' + "); + + $total_eras = DB::select(" + SELECT era_id + FROM all_node_data + WHERE public_key = '$a' + ORDER BY era_id ASC + LIMIT 1 + "); + + $total_eras = (int)($total_eras[0]->era_id ?? 0); + $return["total_eras"] = $current_era_id - $total_eras; + $return["total_bad_marks"] += (int)(count($total_bad_marks ?? array())); + $return["peers"] += (int)($address->peers ?? 0); + $return["uptime"][$a] = (float)($address->uptime ?? 0); + $return["avg_uptime"] += (float)($address->uptime ?? 0); + + if ((int)$address->bid_inactive == 0) { + $return["node_status"] = "Online"; + } + } + + $addresses_count = count($addresses); + $addresses_count = $addresses_count ? $addresses_count : 1; + $return["avg_uptime"] = $return["avg_uptime"] / $addresses_count; + + info($return); + return $this->successResponse($return); + } + + public function getNodesPage() { + $user = auth()->user(); + $user_id = $user->id ?? 0; + + // Get current era + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + // Define complete return object + $return = array( + "mbs" => 0, + "ranking" => array( + "0123456789abcdef" => 0 + ), + "addresses" => array( + "0123456789abcdef" => array( + "stake_amount" => 0, + "delegators" => 0, + "uptime" => 0, + "update_responsiveness" => 100, + "peers" => 0, + "daily_earning" => 0, + "total_eras" => 0, + "eras_sinse_bad_mark" => $current_era_id, + "total_bad_marks" => 0, + "validator_rewards" => array( + "day" => array(), + "week" => array(), + "month" => array(), + "year" => array() + ) + ) + ) + ); + + unset($return["ranking"]["0123456789abcdef"]); + unset($return["addresses"]["0123456789abcdef"]); + $nodeHelper = new NodeHelper(); + + // get ranking + $ranking = DB::select(" + SELECT + public_key, + historical_performance AS uptime, + bid_delegators_count, + bid_delegation_rate, + bid_total_staked_amount + FROM all_node_data + WHERE in_curent_era = 1 + AND in_next_era = 1 + AND in_auction = 1 + AND bad_mark = 0 + "); + $max_delegators = 0; + $max_stake_amount = 0; + + foreach ($ranking as $r) { + if ((int)$r->bid_delegators_count > $max_delegators) { + $max_delegators = (int)$r->bid_delegators_count; + } + + if ((int)$r->bid_total_staked_amount > $max_stake_amount) { + $max_stake_amount = (int)$r->bid_total_staked_amount; + } + } + + foreach ($ranking as $r) { + $uptime_score = ( + 25 * (float)$r->uptime + ) / 100; + $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; + + $fee_score = ( + 25 * + (1 - ((float)$r->bid_delegation_rate / 100)) + ); + $fee_score = $fee_score < 0 ? 0 : $fee_score; + + $count_score = ( + (float)$r->bid_delegators_count / + $max_delegators + ) * 25; + $count_score = $count_score < 0 ? 0 : $count_score; + + $stake_score = ( + (float)$r->bid_total_staked_amount / + $max_stake_amount + ) * 25; + $stake_score = $stake_score < 0 ? 0 : $stake_score; + + $return["ranking"][$r->public_key] = ( + $uptime_score + + $fee_score + + $count_score + + $stake_score + ); + } + + uasort( + $return["ranking"], + function($x, $y) { + if ($x == $y) { + return 0; + } + + return ($x > $y) ? -1 : 1; + } + ); + + $sorted_ranking = array(); + $i = 1; + + foreach ($return["ranking"] as $public_key => $score) { + $sorted_ranking[$public_key] = $i; + $i += 1; + } + + $return["ranking"] = $sorted_ranking; + + $addresses = DB::select(" + SELECT + a.public_key, a.bid_delegators_count, + a.bid_total_staked_amount, a.bid_self_staked_amount, + a.historical_performance AS uptime, + a.port8888_peers AS peers, + a.bid_inactive, a.bad_mark, a.stable + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + JOIN users AS c + ON b.user_id = c.id + WHERE a.era_id = $current_era_id + AND b.user_id = $user_id + "); + + if (!$addresses) { + $addresses = array(); + } + + // for each member's node address + foreach ($addresses as $address) { + $a = $address->public_key ?? ''; + + $eras_sinse_bad_mark = DB::select(" + SELECT a.era_id + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.public_key = '$a' + AND a.bad_mark = 1 + ORDER BY era_id DESC + LIMIT 1 + "); + $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; + $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; + + $total_bad_marks = DB::select(" + SELECT bad_mark + FROM all_node_data + WHERE bad_mark = 1 + AND public_key = '$a' + "); + + $total_eras = DB::select(" + SELECT era_id + FROM all_node_data + WHERE public_key = '$a' + ORDER BY era_id ASC + LIMIT 1 + "); + + $total_eras = (int)($total_eras[0]->era_id ?? 0); + $total_eras = $current_era_id - $total_eras; + + // Calc earning + $one_day_ago = Carbon::now('UTC')->subHours(24); + $daily_earning = DB::select(" + SELECT bid_self_staked_amount + FROM all_node_data + WHERE public_key = '$a' + AND created_at < '$one_day_ago' + ORDER BY era_id DESC + LIMIT 1 + "); + $daily_earning = $daily_earning[0]->bid_self_staked_amount ?? 0; + $daily_earning = $address->bid_self_staked_amount - $daily_earning; + $daily_earning = $daily_earning < 0 ? 0 : $daily_earning; + + $earning_day = $nodeHelper->getValidatorRewards($a, 'day'); + $earning_week = $nodeHelper->getValidatorRewards($a, 'week'); + $earning_month = $nodeHelper->getValidatorRewards($a, 'month'); + $earning_year = $nodeHelper->getValidatorRewards($a, 'year'); + + $return["addresses"][$a] = array( + "stake_amount" => $address->bid_total_staked_amount, + "delegators" => $address->bid_delegators_count, + "uptime" => $address->uptime, + "update_responsiveness" => 100, + "peers" => $address->peers, + "daily_earning" => $daily_earning, + "total_eras" => $total_eras, + "eras_sinse_bad_mark" => $eras_sinse_bad_mark, + "total_bad_marks" => count($total_bad_marks ?? array()), + "validator_rewards" => array( + "day" => $earning_day, + "week" => $earning_week, + "month" => $earning_month, + "year" => $earning_year + ) + ); + } + + // get mbs + $mbs = DB::select(" + SELECT mbs + FROM mbs + ORDER BY era_id DESC + LIMIT 1 + "); + $return["mbs"] = (int)($mbs[0]->mbs ?? 0); + + info($return); + return $this->successResponse($return); + } + + public function getMyEras() { + $user = auth()->user(); + $user_id = $user->id ?? 0; + + // Get current era + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + // define return object + $return = array( + "addresses" => array( + "0123456789abcdef" => array( + "uptime" => 0, + "eras_active" => 0, + "eras_sinse_bad_mark" => $current_era_id, + "total_bad_marks" => 0 + ) + ), + "column_count" => 2, + "eras" => array( + array( + "era_start_time" => '', + "addresses" => array( + "0123456789abcdef" => array( + "in_pool" => false, + "rewards" => 0 + ) + ) + ) + ) + ); + + unset($return["addresses"]["0123456789abcdef"]); + unset($return["eras"][0]); + + // get addresses data + $addresses = DB::select(" + SELECT + a.public_key, + a.historical_performance AS uptime + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + JOIN users AS c + ON b.user_id = c.id + WHERE a.era_id = $current_era_id + AND b.user_id = $user_id + "); + + if (!$addresses) { + $addresses = array(); + } + + // for each member's node address + foreach ($addresses as $address) { + $a = $address->public_key ?? ''; + + $eras_sinse_bad_mark = DB::select(" + SELECT a.era_id + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.public_key = '$a' + AND a.bad_mark = 1 + ORDER BY era_id DESC + LIMIT 1 + "); + $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; + $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; + + $total_bad_marks = DB::select(" + SELECT bad_mark + FROM all_node_data + WHERE bad_mark = 1 + AND public_key = '$a' + "); + + $eras_active = DB::select(" + SELECT era_id + FROM all_node_data + WHERE public_key = '$a' + ORDER BY era_id ASC + LIMIT 1 + "); + + $eras_active = (int)($eras_active[0]->era_id ?? 0); + $eras_active = $current_era_id - $eras_active; + + $return["addresses"][$a] = array( + "uptime" => $address->uptime, + "eras_active" => $eras_active, + "eras_sinse_bad_mark" => $eras_sinse_bad_mark, + "total_bad_marks" => count($total_bad_marks ?? array()) + ); + } + + // get eras table data + $era_minus_360 = $current_era_id - 360; + + if ($era_minus_360 < 1) { + $era_minus_360 = 1; + } + + $eras = DB::select(" + SELECT + a.public_key, a.era_id, a.created_at, + a.in_curent_era, a.in_auction, + a.bid_inactive, a.bad_mark, + a.uptime + FROM all_node_data AS a + JOIN user_addresses + ON a.public_key = user_addresses.public_address_node + JOIN users + ON user_addresses.user_id = users.id + WHERE users.id = $user_id + AND era_id > $era_minus_360 + ORDER BY a.era_id DESC + "); + + if (!$eras) { + $eras = array(); + } + + $sorted_eras = array(); + + // for each node address's era + foreach ($eras as $era) { + $era_id = $era->era_id ?? 0; + $era_start_time = $era->created_at ?? ''; + $public_key = $era->public_key; + + if (!isset($sorted_eras[$era_id])) { + $sorted_eras[$era_id] = array( + "era_start_time" => $era_start_time, + "addresses" => array() + ); + } + + $sorted_eras + [$era_id] + ["addresses"] + [$public_key] = array( + "in_pool" => $era->in_auction, + "rewards" => $era->uptime, + ); + } + + $return["eras"] = $sorted_eras; + $column_count = 0; + + foreach ($return["eras"] as $era) { + $count = $era["addresses"] ? count($era["addresses"]) : 0; + + if ($count > $column_count) { + $column_count = $count; + } + } + + $return["column_count"] = $column_count + 1; + + info($return); + return $this->successResponse($return); + } + public function getMemberCountInfo() { $data = [ 'total' => 0, @@ -109,21 +884,51 @@ public function getMemberCountInfo() { // Get Verified Members public function getVerifiedMembers(Request $request) { - $data = []; - $limit = $request->limit ?? 50; $user = auth()->user(); - $data = User::select([ - 'users.id', - 'users.pseudonym', - 'users.public_address_node', - 'users.node_status', - 'profile.extra_status', - ]) - ->join('profile', 'profile.user_id', '=', 'users.id') - ->where('profile.status', 'approved') - ->whereNotNull('users.public_address_node') - ->paginate($limit); + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + $members = DB::select(" + SELECT + a.public_key, + c.id, c.pseudonym, c.node_status, + d.extra_status + FROM all_node_data AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + JOIN users AS c + ON b.user_id = c.id + JOIN profile AS d + ON c.id = d.user_id + WHERE d.status = 'approved' + AND a.era_id = $current_era_id + "); + info($members); + //// done + + + + + $data = []; + $limit = $request->limit ?? 50; + $data = User::select([ + 'users.id', + 'users.pseudonym', + 'users.public_address_node', + 'users.node_status', + 'profile.extra_status', + ]) + ->join('profile', 'profile.user_id', '=', 'users.id') + ->where('profile.status', 'approved') + ->whereNotNull('users.public_address_node') + ->paginate($limit); + info($data); return $this->successResponse($data); } @@ -157,7 +962,7 @@ public function updateShuftiproStatus() { $profile->status = null; $profile->save(); } - + Shuftipro::where('user_id', $user->id)->delete(); ShuftiproTemp::where('user_id', $user->id)->delete(); } @@ -169,6 +974,7 @@ public function updateShuftiproStatus() { 'verification.accepted', 'verification.declined', ]; + if (isset($data['event']) && in_array($data['event'], $events)) { $user = User::find($recordTemp->user_id); @@ -209,6 +1015,7 @@ public function changeEmail(ChangeEmailRequest $request) 'created_at' => now() ] ); + if ($userVerify) Mail::to($request->email)->send(new UserVerifyMail($code)); DB::commit(); return $this->metaSuccess(); @@ -216,7 +1023,7 @@ public function changeEmail(ChangeEmailRequest $request) return $this->errorResponse(__('api.error.internal_error'), Response::HTTP_INTERNAL_SERVER_ERROR); } } - + public function changePassword(ChangePasswordRequest $request) { $user = auth()->user(); @@ -345,41 +1152,71 @@ public function submitPublicAddress(SubmitPublicAddressRequest $request) $user = auth()->user(); $address = strtolower($request->public_address); $public_address = strtolower($address); - $public_address_temp = (new ChecksumValidator())->do($address); + if (!$public_address_temp) { - return $this->errorResponse(__('The validator ID is invalid'), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __('The validator ID is invalid'), + Response::HTTP_BAD_REQUEST + ); } - + $correct_checksum = (int) (new ChecksumValidator($public_address_temp))->do(); + if (!$correct_checksum) { - return $this->errorResponse(__('The validator ID is invalid'), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __('The validator ID is invalid'), + Response::HTTP_BAD_REQUEST + ); } - + // User Check $tempUser = User::where('public_address_node', $public_address)->first(); - if ($tempUser && $tempUser->id != $user->id && $tempUser->node_verified_at) { - return $this->errorResponse(__('The validator ID you specified is already associated with an Association member'), Response::HTTP_BAD_REQUEST); + + if ( + $tempUser && + $tempUser->id != $user->id && + $tempUser->node_verified_at + ) { + return $this->errorResponse( + __('The validator ID you specified is already associated with an Association member'), + Response::HTTP_BAD_REQUEST + ); } // User Address Check $tempUserAddress = UserAddress::where('public_address_node', $public_address)->first(); - if ($tempUserAddress && $tempUserAddress->user_id != $user->id && $tempUserAddress->node_verified_at) { - return $this->errorResponse(__('The validator ID you specified is already associated with an Association member'), Response::HTTP_BAD_REQUEST); + + if ( + $tempUserAddress && + $tempUserAddress->user_id != $user->id && + $tempUserAddress->node_verified_at + ) { + return $this->errorResponse( + __('The validator ID you specified is already associated with an Association member'), + Response::HTTP_BAD_REQUEST + ); } // Pool Check $nodeHelper = new NodeHelper(); $addresses = $nodeHelper->getValidAddresses(); + if (!in_array($public_address, $addresses)) { - return $this->errorResponse(__('The validator ID specified could not be found in the Casper validator pool'), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __('The validator ID specified could not be found in the Casper validator pool'), + Response::HTTP_BAD_REQUEST + ); } // Remove Other User's Same Address UserAddress::where('public_address_node', $public_address)->where('user_id', '!=', $user->id)->whereNull('node_verified_at')->delete(); User::where('public_address_node', $public_address)->where('id', '!=', $user->id)->whereNull('node_verified_at')->update(['public_address_node' => null]); - if (!$tempUserAddress || $tempUserAddress->user_id != $user->id) { + if ( + !$tempUserAddress || + $tempUserAddress->user_id != $user->id + ) { $userAddress = new UserAddress; $userAddress->user_id = $user->id; $userAddress->public_address_node = $public_address; @@ -397,35 +1234,48 @@ public function submitPublicAddress(SubmitPublicAddressRequest $request) public function checkValidatorAddress(SubmitPublicAddressRequest $request) { $address = strtolower($request->public_address); - $public_address_temp = (new ChecksumValidator())->do($address); $public_address = strtolower($address); if (!$public_address_temp) { - return $this->successResponse(['message' => __('The validator ID is invalid')]); + return $this->successResponse( + ['message' => __('The validator ID is invalid')] + ); } $correct_checksum = (int) (new ChecksumValidator($public_address_temp))->do(); + if (!$correct_checksum) { - return $this->successResponse(['message' => __('The validator ID is invalid')]); + return $this->successResponse( + ['message' => __('The validator ID is invalid')] + ); } // User Check $tempUser = User::where('public_address_node', $public_address)->first(); + if ($tempUser && $tempUser->node_verified_at) { - return $this->successResponse(['message' => __('The validator ID you specified is already associated with an Association member')]); + return $this->successResponse( + ['message' => __('The validator ID you specified is already associated with an Association member')] + ); } // User Address Check $tempUserAddress = UserAddress::where('public_address_node', $public_address)->first(); + if ($tempUserAddress && $tempUserAddress->node_verified_at) { - return $this->successResponse(['message' => __('The validator ID you specified is already associated with an Association member')]); + return $this->successResponse( + ['message' => __('The validator ID you specified is already associated with an Association member')] + ); } $nodeHelper = new NodeHelper(); $addresses = $nodeHelper->getValidAddresses(); + if (!in_array($public_address, $addresses)) { - return $this->successResponse(['message' => __('The validator ID specified could not be found in the Casper validator pool')]); + return $this->successResponse( + ['message' => __('The validator ID specified could not be found in the Casper validator pool')] + ); } return $this->metaSuccess(); } @@ -434,34 +1284,53 @@ public function checkPublicAddress(SubmitPublicAddressRequest $request) { $address = strtolower($request->public_address); $public_address = strtolower($address); - $public_address_temp = (new ChecksumValidator())->do($address); + if (!$public_address_temp) { - return $this->errorResponse(__('The validator ID is invalid'), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __('The validator ID is invalid'), + Response::HTTP_BAD_REQUEST + ); } $correct_checksum = (int) (new ChecksumValidator($public_address_temp))->do(); + if (!$correct_checksum) { - return $this->errorResponse(__('The validator ID is invalid'), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __('The validator ID is invalid'), + Response::HTTP_BAD_REQUEST + ); } // User Check $tempUser = User::where('public_address_node', $public_address)->first(); + if ($tempUser && $tempUser->node_verified_at) { - return $this->errorResponse(__('The validator ID you specified is already associated with an Association member'), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __('The validator ID you specified is already associated with an Association member'), + Response::HTTP_BAD_REQUEST + ); } // User Address Check $tempUserAddress = UserAddress::where('public_address_node', $public_address)->first(); + if ($tempUserAddress && $tempUserAddress->node_verified_at) { - return $this->errorResponse(__('The validator ID you specified is already associated with an Association member'), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __('The validator ID you specified is already associated with an Association member'), + Response::HTTP_BAD_REQUEST + ); } // Pool Check $nodeHelper = new NodeHelper(); $addresses = $nodeHelper->getValidAddresses(); + if (!in_array($public_address, $addresses)) { - return $this->errorResponse(__('The validator ID specified could not be found in the Casper validator pool'), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __('The validator ID specified could not be found in the Casper validator pool'), + Response::HTTP_BAD_REQUEST + ); } return $this->metaSuccess(); @@ -469,11 +1338,12 @@ public function checkPublicAddress(SubmitPublicAddressRequest $request) public function getMessageContent() { - $user = auth()->user(); + $user = auth()->user(); $timestamp = date('d/m/Y'); - $message = "Please use the Casper Signature python tool to sign this message! " . $timestamp; + $message = "Please use the Casper Signature python tool to sign this message! " . $timestamp; $user->update(['message_content' => $message]); - $filename = 'message.txt'; + $filename = 'message.txt'; + return response()->streamDownload(function () use ($message) { echo $message; }, $filename); @@ -482,24 +1352,37 @@ public function getMessageContent() public function verifyFileCasperSigner2(VerifyFileCasperSignerRequest $request) { try { - $casperSigVerify = new CasperSigVerify(); - $user = auth()->user(); - $message = $user->message_content; + $casperSigVerify = new CasperSigVerify(); + $user = auth()->user(); + $message = $user->message_content; $public_validator_key = strtolower($request->address); - $userRecord = User::where('public_address_node', $public_validator_key)->first(); - $userAddress = UserAddress::where('public_address_node', $public_validator_key)->first(); + $userRecord = User::where( + 'public_address_node', + $public_validator_key + )->first(); + + $userAddress = UserAddress::where( + 'public_address_node', + $public_validator_key + )->first(); if ($userRecord && $userRecord->node_verified_at) { - return $this->errorResponse(__($this->failed_verification_response), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __($this->failed_verification_response), + Response::HTTP_BAD_REQUEST + ); } if ($userAddress && $userAddress->node_verified_at) { - return $this->errorResponse(__($this->failed_verification_response), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __($this->failed_verification_response), + Response::HTTP_BAD_REQUEST + ); } - $file = $request->file; - $name = $file->getClientOriginalName(); + $file = $request->file; + $name = $file->getClientOriginalName(); $hexstring = $file->get(); if ($hexstring && $name == 'signature') { @@ -513,32 +1396,47 @@ public function verifyFileCasperSigner2(VerifyFileCasperSignerRequest $request) $filenamehash = md5(Str::random(10) . '_' . (string)time()); $S3 = new S3Client([ - 'version' => 'latest', - 'region' => getenv('AWS_DEFAULT_REGION'), + 'version' => 'latest', + 'region' => getenv('AWS_DEFAULT_REGION'), 'credentials' => [ - 'key' => getenv('AWS_ACCESS_KEY_ID'), - 'secret' => getenv('AWS_SECRET_ACCESS_KEY'), + 'key' => getenv('AWS_ACCESS_KEY_ID'), + 'secret' => getenv('AWS_SECRET_ACCESS_KEY'), ], ]); $s3result = $S3->putObject([ - 'Bucket' => getenv('AWS_BUCKET'), - 'Key' => 'signatures/' . $filenamehash, - 'SourceFile' => $request->file('file') + 'Bucket' => getenv('AWS_BUCKET'), + 'Key' => 'signatures/' . $filenamehash, + 'SourceFile' => $request->file('file') ]); $ObjectURL = $s3result['ObjectURL'] ?? getenv('SITE_URL').'/not-found'; // Remove Other User's Same Address - UserAddress::where('public_address_node', $public_validator_key)->where('user_id', '!=', $user->id)->whereNull('node_verified_at')->delete(); - User::where('public_address_node', $public_validator_key)->where('id', '!=', $user->id)->whereNull('node_verified_at')->update(['public_address_node' => null]); - - $userAddress = UserAddress::where('public_address_node', $public_validator_key)->where('user_id', $user->id)->first(); - if (!$userAddress) $userAddress = new UserAddress; - $userAddress->user_id = $user->id; + UserAddress::where('public_address_node', $public_validator_key) + ->where('user_id', '!=', $user->id) + ->whereNull('node_verified_at') + ->delete(); + + User::where('public_address_node', $public_validator_key) + ->where('id', '!=', $user->id) + ->whereNull('node_verified_at') + ->update(['public_address_node' => null]); + + $userAddress = UserAddress::where( + 'public_address_node', + $public_validator_key) + ->where('user_id', $user->id) + ->first(); + + if (!$userAddress) { + $userAddress = new UserAddress; + } + + $userAddress->user_id = $user->id; $userAddress->public_address_node = $public_validator_key; - $userAddress->signed_file = $ObjectURL; - $userAddress->node_verified_at = now(); + $userAddress->signed_file = $ObjectURL; + $userAddress->node_verified_at = now(); $userAddress->save(); $emailerData = EmailerHelper::getEmailerData(); @@ -552,32 +1450,50 @@ public function verifyFileCasperSigner2(VerifyFileCasperSignerRequest $request) ); return $this->metaSuccess(); } else { - return $this->errorResponse(__($this->failed_verification_response), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __($this->failed_verification_response), + Response::HTTP_BAD_REQUEST + ); } } - return $this->errorResponse(__($this->failed_verification_response), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __($this->failed_verification_response), + Response::HTTP_BAD_REQUEST + ); } catch (\Exception $ex) { - return $this->errorResponse(__($this->failed_verification_response), Response::HTTP_BAD_REQUEST, $ex->getMessage()); + return $this->errorResponse( + __($this->failed_verification_response), + Response::HTTP_BAD_REQUEST, + $ex->getMessage() + ); } } public function verifyFileCasperSigner(VerifyFileCasperSignerRequest $request) { try { - $casperSigVerify = new CasperSigVerify(); - $user = auth()->user(); - $message = $user->message_content; + $casperSigVerify = new CasperSigVerify(); + $user = auth()->user(); + $message = $user->message_content; $public_validator_key = strtolower($request->address); - $userRecord = User::where('id', $user->id)->where('public_address_node', $public_validator_key)->first(); - $userAddress = UserAddress::where('user_id', $user->id)->where('public_address_node', $public_validator_key)->first(); + $userRecord = User::where('id', $user->id) + ->where('public_address_node', $public_validator_key) + ->first(); + + $userAddress = UserAddress::where('user_id', $user->id) + ->where('public_address_node', $public_validator_key) + ->first(); + if (!$userRecord || !$userAddress) { - return $this->errorResponse(__($this->failed_verification_response), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __($this->failed_verification_response), + Response::HTTP_BAD_REQUEST + ); } - $file = $request->file; - - $name = $file->getClientOriginalName(); + $file = $request->file; + $name = $file->getClientOriginalName(); $hexstring = $file->get(); if ($hexstring && $name == 'signature') { @@ -586,64 +1502,93 @@ public function verifyFileCasperSigner(VerifyFileCasperSignerRequest $request) $public_validator_key, $message ); + if ($verified) { $filenamehash = md5(Str::random(10) . '_' . (string)time()); // S3 file upload $S3 = new S3Client([ - 'version' => 'latest', - 'region' => getenv('AWS_DEFAULT_REGION'), + 'version' => 'latest', + 'region' => getenv('AWS_DEFAULT_REGION'), 'credentials' => [ - 'key' => getenv('AWS_ACCESS_KEY_ID'), - 'secret' => getenv('AWS_SECRET_ACCESS_KEY'), + 'key' => getenv('AWS_ACCESS_KEY_ID'), + 'secret' => getenv('AWS_SECRET_ACCESS_KEY'), ], ]); $s3result = $S3->putObject([ - 'Bucket' => getenv('AWS_BUCKET'), - 'Key' => 'signatures/' . $filenamehash, - 'SourceFile' => $request->file('file') + 'Bucket' => getenv('AWS_BUCKET'), + 'Key' => 'signatures/' . $filenamehash, + 'SourceFile' => $request->file('file') ]); $ObjectURL = $s3result['ObjectURL'] ?? getenv('SITE_URL').'/not-found'; - - $user->signed_file = $ObjectURL; - $user->has_verified_address = 1; - $user->node_verified_at = now(); + + $user->signed_file = $ObjectURL; + $user->has_verified_address = 1; + $user->node_verified_at = now(); $user->save(); - $userAddress->signed_file = $ObjectURL; + $userAddress->signed_file = $ObjectURL; $userAddress->node_verified_at = now(); $userAddress->save(); $emailerData = EmailerHelper::getEmailerData(); - EmailerHelper::triggerUserEmail($user->email, 'Your Node is Verified', $emailerData, $user); - if ($user->letter_verified_at && $user->signature_request_id && $user->node_verified_at) - EmailerHelper::triggerUserEmail($user->email, 'Congratulations', $emailerData, $user); + EmailerHelper::triggerUserEmail( + $user->email, + 'Your Node is Verified', + $emailerData, + $user + ); + + if ( + $user->letter_verified_at && + $user->signature_request_id && + $user->node_verified_at + ) { + EmailerHelper::triggerUserEmail( + $user->email, + 'Congratulations', + $emailerData, + $user + ); + } return $this->metaSuccess(); } else { - return $this->errorResponse(__($this->failed_verification_response), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __($this->failed_verification_response), + Response::HTTP_BAD_REQUEST + ); } } - return $this->errorResponse(__($this->failed_verification_response), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __($this->failed_verification_response), + Response::HTTP_BAD_REQUEST + ); } catch (\Exception $ex) { - return $this->errorResponse(__($this->failed_verification_response), Response::HTTP_BAD_REQUEST, $ex->getMessage()); + return $this->errorResponse( + __($this->failed_verification_response), + Response::HTTP_BAD_REQUEST, + $ex->getMessage() + ); } } public function functionSubmitKYC(SubmitKYCRequest $request) { - $user = auth()->user(); - $data = $request->validated(); + $user = auth()->user(); + $data = $request->validated(); $data['dob'] = \Carbon\Carbon::parse($request->dob)->format('Y-m-d'); $user->update(['member_status' => User::STATUS_INCOMPLETE]); + $this->profileRepo->updateOrCreate( [ 'user_id' => $user->id, ], $data ); + $user->reset_kyc = 0; $user->save(); return $this->metaSuccess(); @@ -652,44 +1597,62 @@ public function functionSubmitKYC(SubmitKYCRequest $request) public function verifyOwnerNode(Request $request) { $user = auth()->user(); + $this->profileRepo->updateConditions( ['type_owner_node' => $request->type], ['user_id' => $user->id] ); + return $this->metaSuccess(); } public function getOwnerNodes() { - $user = auth()->user(); + $user = auth()->user(); $owners = OwnerNode::where('user_id', $user->id)->get(); + foreach ($owners as $owner) { - $email = $owner->email; + $email = $owner->email; $userOwner = User::where('email', $email)->first(); - if ($userOwner) $owner->kyc_verified_at = $userOwner->kyc_verified_at; - else $owner->kyc_verified_at = null; + + if ($userOwner) { + $owner->kyc_verified_at = $userOwner->kyc_verified_at; + } else { + $owner->kyc_verified_at = null; + } } - $data = []; + + $data = []; $data['kyc_verified_at'] = $user->kyc_verified_at; - $data['owner_node'] = $owners; + $data['owner_node'] = $owners; return $this->successResponse($data); } public function resendEmailOwnerNodes(ResendEmailRequest $request) { - $user = auth()->user(); - $email = $request->email; - $owners = OwnerNode::where('user_id', $user->id)->where('email', $email)->first(); + $user = auth()->user(); + $email = $request->email; + $owners = OwnerNode::where('user_id', $user->id) + ->where('email', $email) + ->first(); + if ($owners) { $userOwner = User::where('email', $email)->first(); + if (!$userOwner) { - $url = $request->header('origin') ?? $request->root(); + $url = $request->header('origin') ?? $request->root(); $resetUrl = $url . '/register-type'; + Mail::to($email)->send(new AddNodeMail($resetUrl)); } - } else - return $this->errorResponse('Email does not exist', Response::HTTP_BAD_REQUEST); + } else { + return $this->errorResponse( + 'Email does not exist', + Response::HTTP_BAD_REQUEST + ); + } + return $this->successResponse(null); } @@ -697,19 +1660,23 @@ public function resendEmailOwnerNodes(ResendEmailRequest $request) public function saveShuftiproTemp(Request $request) { $user = auth()->user(); + // Validator $validator = Validator::make($request->all(), [ 'reference_id' => 'required' ]); - if ($validator->fails()) return $this->validateResponse($validator->errors()); - $user_id = $user->id; - $reference_id = $request->reference_id; + if ($validator->fails()) { + return $this->validateResponse($validator->errors()); + } + + $user_id = $user->id; + $reference_id = $request->reference_id; ShuftiproTemp::where('user_id', $user_id)->delete(); - $record = new ShuftiproTemp; - $record->user_id = $user_id; + $record = new ShuftiproTemp; + $record->user_id = $user_id; $record->reference_id = $reference_id; $record->save(); @@ -720,15 +1687,20 @@ public function saveShuftiproTemp(Request $request) public function deleteShuftiproTemp(Request $request) { $user = auth()->user(); + // Validator $validator = Validator::make($request->all(), [ 'reference_id' => 'required' ]); - if ($validator->fails()) return $this->validateResponse($validator->errors()); - $user_id = $user->id; + if ($validator->fails()) { + return $this->validateResponse($validator->errors()); + } + + $user_id = $user->id; $reference_id = $request->reference_id; - $profile = Profile::where('user_id', $user_id)->first(); + $profile = Profile::where('user_id', $user_id)->first(); + if ($profile) { $profile->status = null; $profile->save(); @@ -744,71 +1716,101 @@ public function deleteShuftiproTemp(Request $request) public function updateShuftiProTemp(Request $request) { $user = auth()->user(); + // Validator $validator = Validator::make($request->all(), [ 'reference_id' => 'required' ]); - if ($validator->fails()) return $this->validateResponse($validator->errors()); - $user_id = $user->id; + if ($validator->fails()) { + return $this->validateResponse($validator->errors()); + } + + $user_id = $user->id; $reference_id = $request->reference_id; - $profile = Profile::where('user_id', $user_id)->first(); + $profile = Profile::where('user_id', $user_id)->first(); + if ($profile) { $profile->status = 'pending'; $profile->save(); } + $record = ShuftiproTemp::where('user_id', $user_id) - ->where('reference_id', $reference_id) - ->first(); + ->where('reference_id', $reference_id) + ->first(); + if ($record) { $record->status = 'booked'; $record->save(); - $emailerData = EmailerHelper::getEmailerData(); - EmailerHelper::triggerAdminEmail('KYC or AML need review', $emailerData, $user); + $emailerData = EmailerHelper::getEmailerData(); + + EmailerHelper::triggerAdminEmail( + 'KYC or AML need review', + $emailerData, + $user + ); + return $this->metaSuccess(); } - return $this->errorResponse('Fail submit AML', Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + 'Fail submit AML', + Response::HTTP_BAD_REQUEST + ); } // get vote list public function getVotes(Request $request) { - $status = $request->status ?? 'active'; - $limit = $request->limit ?? 50; - $sort_key = $request->sort_key ?? ''; - $sort_direction = $request->sort_direction ?? ''; - if (!$sort_key) $sort_key = 'ballot.id'; - if (!$sort_direction) $sort_direction = 'desc'; - - if ($status != 'active' && $status != 'finish' && $status != 'scheduled') - return $this->errorResponse('Paramater invalid (status is active or finish)', Response::HTTP_BAD_REQUEST); + $status = $request->status ?? 'active'; + $limit = $request->limit ?? 50; + $sort_key = $request->sort_key ?? 'ballot.id'; + $sort_direction = $request->sort_direction ?? 'desc'; + + if ( + $status != 'active' && + $status != 'finish' && + $status != 'scheduled' + ) { + return $this->errorResponse( + 'Paramater invalid (status is active or finish)', + Response::HTTP_BAD_REQUEST + ); + } - $now = Carbon::now('EST'); + $now = Carbon::now('EST'); $startDate = $now->format('Y-m-d'); $startTime = $now->format('H:i:s'); if ($status == 'active') { $query = Ballot::where('status', 'active') - ->where(function ($query) use ($startDate, $startTime) { - $query->where('start_date', '<', $startDate) - ->orWhere(function ($query) use ($startDate, $startTime) { - $query->where('start_date', $startDate) - ->where('start_time', '<=', $startTime); - }); - }); - } else if ($status == 'scheduled') { + ->where(function ($query) use ($startDate, $startTime) { + $query->where('start_date', '<', $startDate) + ->orWhere(function ($query) use ($startDate, $startTime) { + $query->where('start_date', $startDate) + ->where('start_time', '<=', $startTime); + }); + }); + } + + else if ($status == 'scheduled') { $query = Ballot::where('status', 'active') - ->where(function ($query) use ($startDate, $startTime) { - $query->where('start_date', '>', $startDate) - ->orWhere(function ($query) use ($startDate, $startTime) { - $query->where('start_date', $startDate) - ->where('start_time', '>', $startTime); - }); - }); + ->where(function ($query) use ($startDate, $startTime) { + $query->where('start_date', '>', $startDate) + ->orWhere(function ($query) use ($startDate, $startTime) { + $query->where('start_date', $startDate) + ->where('start_time', '>', $startTime); + }); + }); } - else $query = Ballot::where('status', '<>', 'active'); - $data = $query->with('vote')->orderBy($sort_key, $sort_direction)->paginate($limit); + else { + $query = Ballot::where('status', '<>', 'active'); + } + + $data = $query->with('vote')->orderBy( + $sort_key, + $sort_direction + )->paginate($limit); return $this->successResponse($data); } @@ -816,15 +1818,30 @@ public function getVotes(Request $request) // get vote detail public function getVoteDetail($id) { - $user = auth()->user(); - $ballot = Ballot::with(['vote', 'voteResults.user', 'files'])->where('id', $id)->first(); - if (!$ballot) - return $this->errorResponse('Not found ballot', Response::HTTP_BAD_REQUEST); + $user = auth()->user(); + $ballot = Ballot::with(['vote', 'voteResults.user', 'files']) + ->where('id', $id) + ->first(); + + if (!$ballot) { + return $this->errorResponse( + 'Not found ballot', + Response::HTTP_BAD_REQUEST + ); + } + foreach ($ballot->files as $file) { - $ballotFileView = BallotFileView::where('ballot_file_id', $file->id)->where('user_id', $user->id)->first(); - $file->is_viewed = $ballotFileView ? 1 : 0; + $ballotFileView = BallotFileView::where('ballot_file_id', $file->id) + ->where('user_id', $user->id) + ->first(); + + $file->is_viewed = $ballotFileView ? 1 : 0; } - $ballot->user_vote = VoteResult::where('user_id', $user->id)->where('ballot_id', $ballot->id)->first(); + + $ballot->user_vote = VoteResult::where('user_id', $user->id) + ->where('ballot_id', $ballot->id) + ->first(); + return $this->successResponse($ballot); } @@ -833,40 +1850,96 @@ public function vote($id, Request $request) { $user = auth()->user(); $vote = $request->vote; - if (!$vote || ($vote != 'for' && $vote != 'against')) - return $this->errorResponse('Paramater invalid (vote is for or against)', Response::HTTP_BAD_REQUEST); + + if ( + !$vote || ( + $vote != 'for' && + $vote != 'against' + ) + ) { + return $this->errorResponse( + 'Paramater invalid (vote is for or against)', + Response::HTTP_BAD_REQUEST + ); + } + + // New check for stable member validator + $stable = false; + $addresses = UserAddress::where('user_id', $user->id)->get(); + + if (!$addresses) { + $addresses = array(); + } + + foreach ($addresses as $address) { + $stable_check = DB::select(" + SELECT stable + FROM all_node_data + WHERE public_key = '$address' + ORDER BY era_id DESC + LIMIT 1 + "); + + if ((bool)($stable_check[0]->stable ?? 0)) { + $stable = true; + } + } + + if (!$stable) { + return $this->errorResponse( + 'Validator is not stable enough to vote', + Response::HTTP_BAD_REQUEST + ); + } + $ballot = Ballot::where('id', $id)->first(); - if (!$ballot) return $this->errorResponse('Not found ballot', Response::HTTP_BAD_REQUEST); - $voteResult = VoteResult::where('user_id', $user->id)->where('ballot_id', $ballot->id)->first(); + + if (!$ballot) { + return $this->errorResponse( + 'Not found ballot', + Response::HTTP_BAD_REQUEST + ); + } + + $voteResult = VoteResult::where('user_id', $user->id) + ->where('ballot_id', $ballot->id) + ->first(); + if ($voteResult) { - if ($vote == $voteResult->type) return $this->metaSuccess(); - else { - $voteResult->type = $vote; + if ($vote == $voteResult->type) { + return $this->metaSuccess(); + } else { + $voteResult->type = $vote; $voteResult->updated_at = now(); + if ($vote == 'for') { - $ballot->vote->for_value = $ballot->vote->for_value + 1; + $ballot->vote->for_value = $ballot->vote->for_value + 1; $ballot->vote->against_value = $ballot->vote->against_value - 1; } else { - $ballot->vote->for_value = $ballot->vote->for_value - 1; + $ballot->vote->for_value = $ballot->vote->for_value - 1; $ballot->vote->against_value = $ballot->vote->against_value + 1; } + $ballot->vote->updated_at = now(); $ballot->vote->save(); $voteResult->save(); } } else { $voteResult = new VoteResult(); - $voteResult->user_id = $user->id; + $voteResult->user_id = $user->id; $voteResult->ballot_id = $ballot->id; - $voteResult->vote_id = $ballot->vote->id; - $voteResult->type = $vote; + $voteResult->vote_id = $ballot->vote->id; + $voteResult->type = $vote; $voteResult->save(); - if ($vote == 'for') - $ballot->vote->for_value = $ballot->vote->for_value + 1; - else + + if ($vote == 'for') { + $ballot->vote->for_value = $ballot->vote->for_value + 1; + } else { $ballot->vote->against_value = $ballot->vote->against_value + 1; + } + $ballot->vote->result_count = $ballot->vote->result_count + 1; - $ballot->vote->updated_at = now(); + $ballot->vote->updated_at = now(); $ballot->vote->save(); } return $this->metaSuccess(); @@ -874,16 +1947,28 @@ public function vote($id, Request $request) public function submitViewFileBallot(Request $request, $fileId) { - $user = auth()->user(); + $user = auth()->user(); $ballotFile = BallotFile::where('id', $fileId)->first(); - if (!$ballotFile) - return $this->errorResponse('Not found ballot file', Response::HTTP_BAD_REQUEST); - $ballotFileView = BallotFileView::where('ballot_file_id', $ballotFile->id)->where('user_id', $user->id)->first(); - if ($ballotFileView) return $this->metaSuccess(); - $ballotFileView = new BallotFileView(); - $ballotFileView->ballot_file_id = $ballotFile->id; - $ballotFileView->ballot_id = $ballotFile->ballot_id; - $ballotFileView->user_id = $user->id; + + if (!$ballotFile) { + return $this->errorResponse( + 'Not found ballot file', + Response::HTTP_BAD_REQUEST + ); + } + + $ballotFileView = BallotFileView::where('ballot_file_id', $ballotFile->id) + ->where('user_id', $user->id) + ->first(); + + if ($ballotFileView) { + return $this->metaSuccess(); + } + + $ballotFileView = new BallotFileView(); + $ballotFileView->ballot_file_id = $ballotFile->id; + $ballotFileView->ballot_id = $ballotFile->ballot_id; + $ballotFileView->user_id = $user->id; $ballotFileView->save(); return $this->metaSuccess(); } @@ -902,29 +1987,29 @@ public function uploadAvatar(Request $request) return $this->validateResponse($validator->errors()); } - $user = auth()->user(); + $user = auth()->user(); $filenameWithExt = $request->file('avatar')->getClientOriginalName(); - $filename = pathinfo($filenameWithExt, PATHINFO_FILENAME); - $extension = $request->file('avatar')->getClientOriginalExtension(); + $filename = pathinfo($filenameWithExt, PATHINFO_FILENAME); + $extension = $request->file('avatar')->getClientOriginalExtension(); // new filename hash - $filenamehash = md5(Str::random(10) . '_' . (string)time()); + $filenamehash = md5(Str::random(10) . '_' . (string)time()); // Filename to store $fileNameToStore = $filenamehash . '.' . $extension; // S3 file upload $S3 = new S3Client([ - 'version' => 'latest', - 'region' => getenv('AWS_DEFAULT_REGION'), + 'version' => 'latest', + 'region' => getenv('AWS_DEFAULT_REGION'), 'credentials' => [ - 'key' => getenv('AWS_ACCESS_KEY_ID'), - 'secret' => getenv('AWS_SECRET_ACCESS_KEY'), + 'key' => getenv('AWS_ACCESS_KEY_ID'), + 'secret' => getenv('AWS_SECRET_ACCESS_KEY'), ], ]); $s3result = $S3->putObject([ - 'Bucket' => getenv('AWS_BUCKET'), - 'Key' => 'client_uploads/' . $fileNameToStore, - 'SourceFile' => $request->file('avatar'), + 'Bucket' => getenv('AWS_BUCKET'), + 'Key' => 'client_uploads/' . $fileNameToStore, + 'SourceFile' => $request->file('avatar'), ]); // $ObjectURL = 'https://'.getenv('AWS_BUCKET').'.s3.amazonaws.com/client_uploads/'.$fileNameToStore; @@ -932,79 +2017,274 @@ public function uploadAvatar(Request $request) $user->save(); return $this->metaSuccess(); } catch (\Exception $ex) { - return $this->errorResponse(__('Failed upload avatar'), Response::HTTP_BAD_REQUEST, $ex->getMessage()); + return $this->errorResponse( + __('Failed upload avatar'), + Response::HTTP_BAD_REQUEST, + $ex->getMessage() + ); } } public function getMembers(Request $request) { + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + $members = DB::table('users') + ->select( + 'users.pseudonym', + 'users.created_at', + 'user_addresses.node_verified_at', + 'all_node_data.public_key', + 'all_node_data.uptime', + 'all_node_data.historical_performance', + 'all_node_data.stable', + 'all_node_data.bid_delegators_count', + 'all_node_data.bid_delegation_rate', + 'all_node_data.bid_total_staked_amount' + ) + ->join( + 'user_addresses', + 'user_addresses.user_id', + '=', + 'users.id' + ) + ->join( + 'all_node_data', + 'all_node_data.public_key', + '=', + 'user_addresses.public_address_node' + ) + ->where([ + 'users.banned' => 0, + 'all_node_data.era_id' => $current_era_id + ]) + ->get(); + + $max_delegators = 0; + $max_stake_amount = 0; + + foreach ($members as $member) { + if ((int)$member->bid_delegators_count > $max_delegators) { + $max_delegators = (int)$member->bid_delegators_count; + } + + if ((int)$member->bid_total_staked_amount > $max_stake_amount) { + $max_stake_amount = (int)$member->bid_total_staked_amount; + } + } + + foreach ($members as &$member) { + $uptime_score = ( + ($request->uptime ?? 0) * + (float)$member->historical_performance + ) / 100; + $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; + + $fee_score = ( + ($request->delegation_rate ?? 0) * + (1 - ((float)$member->bid_delegation_rate / 100)) + ); + $fee_score = $fee_score < 0 ? 0 : $fee_score; + + $count_score = ( + (float)$member->bid_delegators_count / + $max_delegators + ) * ($request->delegators ?? 0); + $count_score = $count_score < 0 ? 0 : $count_score; + + $stake_score = ( + (float)$member->bid_total_staked_amount / + $max_stake_amount + ) * ($request->stake_amount ?? 0); + $stake_score = $stake_score < 0 ? 0 : $stake_score; + + $member->total_score = ( + $uptime_score + + $fee_score + + $count_score + + $stake_score + ); + } + + info($members); + //// done + + + $search = $request->search; - $limit = $request->limit ?? 50; - - $slide_value_uptime = $request->uptime ?? 0; + $limit = $request->limit ?? 50; + + $slide_value_uptime = $request->uptime ?? 0; $slide_value_update_responsiveness = $request->update_responsiveness ?? 0; - $slide_value_delegotors = $request->delegators ?? 0; - $slide_value_stake_amount = $request->stake_amount ?? 0; - $slide_delegation_rate = $request->delegation_rate ?? 0; + $slide_value_delegotors = $request->delegators ?? 0; + $slide_value_stake_amount = $request->stake_amount ?? 0; + $slide_delegation_rate = $request->delegation_rate ?? 0; - $max_uptime = Node::max('uptime'); - $max_uptime = $max_uptime * 100; - + $max_uptime = Node::max('uptime'); + $max_uptime = $max_uptime * 100; $max_delegators = NodeInfo::max('delegators_count'); - if(!$max_delegators || $max_delegators < 1) $max_delegators = 1; - + + if(!$max_delegators || $max_delegators < 1) { + $max_delegators = 1; + } + $max_stake_amount = NodeInfo::max('total_staked_amount'); - if(!$max_stake_amount || $max_stake_amount < 1) $max_stake_amount = 1; + + if(!$max_stake_amount || $max_stake_amount < 1) { + $max_stake_amount = 1; + } $sort_key = $request->sort_key ?? 'created_at'; - + $users = User::with(['metric', 'nodeInfo', 'profile']) - ->whereHas('nodeInfo') - ->where('role', 'member') - ->where(function ($query) use ($search) { - if ($search) { - $query->where('users.first_name', 'like', '%' . $search . '%') - ->orWhere('users.last_name', 'like', '%' . $search . '%'); - } - }) - ->get(); + ->whereHas('nodeInfo') + ->where('role', 'member') + ->where(function ($query) use ($search) { + if ($search) { + $query->where('users.first_name', 'like', '%' . $search . '%') + ->orWhere('users.last_name', 'like', '%' . $search . '%'); + } + }) + ->get(); foreach ($users as $user) { - $latest = Node::where('node_address', strtolower($user->public_address_node)) - ->whereNotnull('protocol_version') - ->orderBy('created_at', 'desc') - ->first(); - if (!$latest) $latest = new Node(); + unset($user['email_verified_at']); + unset($user['last_login_at']); + unset($user['last_login_ip_address']); + unset($user['twoFA_login']); + unset($user['twoFA_login_active']); + + $latest = Node::where( + 'node_address', + strtolower($user->public_address_node) + ) + ->whereNotnull('protocol_version') + ->orderBy('created_at', 'desc') + ->first(); + + if (!$latest) { + $latest = new Node(); + } - $user->status = isset($user->profile) && isset($user->profile->status) ? $user->profile->status : ''; + $user->status = ( + isset($user->profile) && + isset($user->profile->status) ? + $user->profile->status : '' + ); $uptime_nodeInfo = $user->nodeInfo->uptime; - $uptime_node = isset($latest->uptime) && $latest->uptime ? $latest->uptime * 100 : null; - $uptime_metric = isset($user->metric) && isset($user->metric->uptime) ? $user->metric->uptime : null; + $uptime_node = ( + isset($latest->uptime) && + $latest->uptime ? + $latest->uptime * 100 : + null + ); + + $uptime_metric = ( + isset($user->metric) && + isset($user->metric->uptime) ? + $user->metric->uptime : + null + ); $res_nodeInfo = $user->nodeInfo->update_responsiveness ?? null; - $res_node = $latest->update_responsiveness ?? null; - $res_metric = $user->metric->update_responsiveness ?? null; - - $uptime = $uptime_nodeInfo ? $uptime_nodeInfo : ($uptime_node ? $uptime_node : ($uptime_metric ? $uptime_metric : 1)); - $res = $res_nodeInfo ? $res_nodeInfo : ($res_node ? $res_node : ($res_metric ? $res_metric : 0)); - - $delegation_rate = isset($user->nodeInfo->delegation_rate) && $user->nodeInfo->delegation_rate ? $user->nodeInfo->delegation_rate / 100 : 1; - if ($delegation_rate > 1) $delegation_rate = 1; - $delegators_count = isset($user->nodeInfo->delegators_count) && $user->nodeInfo->delegators_count ? $user->nodeInfo->delegators_count : 0; - $total_staked_amount = isset($user->nodeInfo->total_staked_amount) && $user->nodeInfo->total_staked_amount ? $user->nodeInfo->total_staked_amount : 0; - - $uptime_score = (float) (($slide_value_uptime * $uptime) / 100); - $delegation_rate_score = (float) (($slide_delegation_rate * (1 - $delegation_rate)) / 100); - $delegators_count_score = (float) ($delegators_count / $max_delegators) * $slide_value_delegotors; - $total_staked_amount_score = (float) ($total_staked_amount / $max_stake_amount) * $slide_value_stake_amount; - $res_score = (float) (($slide_value_update_responsiveness * $res) / 100); + $res_node = $latest->update_responsiveness ?? null; + $res_metric = $user->metric->update_responsiveness ?? null; + + $uptime = ( + $uptime_nodeInfo ? + $uptime_nodeInfo : ( + $uptime_node ? + $uptime_node : ( + $uptime_metric ? + $uptime_metric : + 1 + ) + ) + ); + + $res = ( + $res_nodeInfo ? + $res_nodeInfo : ( + $res_node ? + $res_node : ( + $res_metric ? + $res_metric : + 0 + ) + ) + ); + + $delegation_rate = ( + isset($user->nodeInfo->delegation_rate) && + $user->nodeInfo->delegation_rate ? + $user->nodeInfo->delegation_rate / 100 : + 1 + ); + + if ($delegation_rate > 1) { + $delegation_rate = 1; + } + + $delegators_count = ( + isset($user->nodeInfo->delegators_count) && + $user->nodeInfo->delegators_count ? + $user->nodeInfo->delegators_count : + 0 + ); + + $total_staked_amount = ( + isset($user->nodeInfo->total_staked_amount) && + $user->nodeInfo->total_staked_amount ? + $user->nodeInfo->total_staked_amount : + 0 + ); + + $uptime_score = (float)( + ($slide_value_uptime * $uptime) / + 100 + ); + + $delegation_rate_score = (float)( + ( + $slide_delegation_rate * + (1 - $delegation_rate) + ) / + 100 + ); + + $delegators_count_score = (float)( + ($delegators_count / $max_delegators) * + $slide_value_delegotors + ); + + $total_staked_amount_score = (float)( + ($total_staked_amount / $max_stake_amount) * + $slide_value_stake_amount + ); + + $res_score = (float)( + ($slide_value_update_responsiveness * $res) / + 100 + ); - $user->uptime = $uptime; - $user->delegation_rate = $delegation_rate; - $user->delegators_count = $delegators_count; + $user->uptime = $uptime; + $user->delegation_rate = $delegation_rate; + $user->delegators_count = $delegators_count; $user->total_staked_amount = $total_staked_amount; - $user->totalScore = $uptime_score + $delegation_rate_score + $delegators_count_score + $total_staked_amount_score + $res_score; + $user->totalScore = ( + $uptime_score + + $delegation_rate_score + + $delegators_count_score + + $total_staked_amount_score + + $res_score + ); } $users = $users->sortByDesc($sort_key)->values(); @@ -1015,17 +2295,58 @@ public function getMembers(Request $request) public function getMemberDetail($id, Request $request) { $public_address_node = $request->get('public_address_node') ?? null; + + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + $response = DB::select(" + SELECT + a.public_address_node, a.node_verified_at, + b.email, b.first_name, b.last_name, b.created_at, + b.kyc_verified_at, b.entity_name, + c.historical_performance AS uptime, c.bid_delegators_count, + c.bid_delegation_rate, c.bid_self_staked_amount, c.bid_total_staked_amount, + d.casper_association_kyc_hash, d.blockchain_name, d.blockchain_desc + FROM user_addresses AS a + JOIN users AS b + ON a.user_id = b.id + JOIN all_node_data AS c + ON a.public_address_node = c.public_key + JOIN profile AS d + ON b.id = d.user_id + WHERE ( + a.public_address_node = '$public_address_node' AND + c.era_id = $current_era_id + ) OR b.id = $id + "); + info($response); + //// done + + + + $user = User::where('id', $id)->first(); + if (!$user || $user->role == 'admin') { + return $this->errorResponse( + __('api.error.not_found'), + Response::HTTP_NOT_FOUND + ); + } + Helper::getAccountInfoStandard($user); - if (!$user || $user->role == 'admin') - return $this->errorResponse(__('api.error.not_found'), Response::HTTP_NOT_FOUND); $user->metric = Helper::getNodeInfo($user, $public_address_node); - $response = $user->load(['profile', 'addresses']); + $response = $user->load(['profile', 'addresses']); unset($response->last_login_at); unset($response->last_login_ip_address); + unset($response->email_verified_at); if (isset($response->profile)) { unset($response->profile->dob); @@ -1039,12 +2360,19 @@ public function getMemberDetail($id, Request $request) public function getCaKycHash($hash) { - if (!ctype_xdigit($hash)) - return $this->errorResponse(__('api.error.not_found'), Response::HTTP_NOT_FOUND); + if (!ctype_xdigit($hash)) { + return $this->errorResponse( + __('api.error.not_found'), + Response::HTTP_NOT_FOUND + ); + } $selection = DB::select(" - SELECT a.casper_association_kyc_hash as proof_hash, b.reference_id, b.status, c.pseudonym - FROM profile as a + SELECT + a.casper_association_kyc_hash AS proof_hash, + b.reference_id, b.status, + c.pseudonym + FROM profile AS a LEFT JOIN shuftipro AS b ON a.user_id = b.user_id LEFT JOIN users AS c @@ -1059,8 +2387,8 @@ public function getCaKycHash($hash) public function getMyVotes(Request $request) { $limit = $request->limit ?? 50; - $user = auth()->user(); - $data = VoteResult::where('vote_result.user_id', $user->id) + $user = auth()->user(); + $data = VoteResult::where('vote_result.user_id', $user->id) ->join('ballot', function ($query) use ($user) { $query->on('vote_result.ballot_id', '=', 'ballot.id'); }) @@ -1079,78 +2407,140 @@ public function getMyVotes(Request $request) public function checkCurrentPassword(Request $request) { $user = auth()->user(); - if (Hash::check($request->current_password, $user->password)) + + if (Hash::check($request->current_password, $user->password)) { return $this->metaSuccess(); - else - return $this->errorResponse(__('Invalid password'), Response::HTTP_BAD_REQUEST); + } else { + return $this->errorResponse( + __('Invalid password'), + Response::HTTP_BAD_REQUEST + ); + } } public function settingUser(Request $request) { $user = auth()->user(); - if ($request->new_password) $user->password = bcrypt($request->new_password); + + if ($request->new_password) { + $user->password = bcrypt($request->new_password); + } if ($request->username) { $checkUsername = User::where('username', $request->username) - ->where('username', '!=', $user->username) - ->first(); - if ($checkUsername) - return $this->errorResponse(__('this username has already been taken'), Response::HTTP_BAD_REQUEST); + ->where('username', '!=', $user->username) + ->first(); + + if ($checkUsername) { + return $this->errorResponse( + __('this username has already been taken'), + Response::HTTP_BAD_REQUEST + ); + } + $user->username = $request->username; } - if (isset($request->twoFA_login)) $user->twoFA_login = $request->twoFA_login; + if (isset($request->twoFA_login)) { + $user->twoFA_login = $request->twoFA_login; + } if ($request->email && $request->email != $user->email) { $emailParam = $request->email; - $checkEmail = User::where(function ($query) use ($emailParam) { - $query->where('email', $emailParam) - ->orWhere('new_email', $emailParam); - }) - ->where('id', '!=', $user->id) - ->first(); + $checkEmail = User::where( + function ($query) use ($emailParam) { + $query->where('email', $emailParam) + ->orWhere('new_email', $emailParam); + } + ) + ->where('id', '!=', $user->id) + ->first(); $currentEmail = $user->email; - $newEmail = $request->email; - if ($checkEmail) - return $this->errorResponse(__('this email has already been taken'), Response::HTTP_BAD_REQUEST); + $newEmail = $request->email; + + if ($checkEmail) { + return $this->errorResponse( + __('this email has already been taken'), + Response::HTTP_BAD_REQUEST + ); + } + $user->new_email = $newEmail; // Current Email $codeCurrentEmail = Str::random(6); - $url = $request->header('origin') ?? $request->root(); - $urlCurrentEmail = $url . '/change-email/cancel-changes?code=' . $codeCurrentEmail . '&email=' . urlencode($currentEmail); + $url = $request->header('origin') ?? $request->root(); + $urlCurrentEmail = ( + $url . + '/change-email/cancel-changes?code=' . + $codeCurrentEmail . + '&email=' . + urlencode($currentEmail) + ); + $newMemberData = [ - 'title' => 'Are you trying to update your email?', + 'title' => 'Are you trying to update your email?', 'content' => 'You recently requested to update your email address with the Casper Association Portal. If this is correct, click the link sent to your new email address to activate it.
If you did not initiate this update, your account could be compromised. Click the button to cancel the change', - 'url' => $urlCurrentEmail, - 'action' => 'cancel' + 'url' => $urlCurrentEmail, + 'action' => 'cancel' ]; - Mail::to($currentEmail)->send(new UserConfirmEmail($newMemberData['title'], $newMemberData['content'], $newMemberData['url'], $newMemberData['action'])); - VerifyUser::where('email', $currentEmail)->where('type', VerifyUser::TYPE_CANCEL_EMAIL)->delete(); - $verify = new VerifyUser(); - $verify->code = $codeCurrentEmail; - $verify->email = $currentEmail; - $verify->type = VerifyUser::TYPE_CANCEL_EMAIL; + + Mail::to($currentEmail)->send( + new UserConfirmEmail( + $newMemberData['title'], + $newMemberData['content'], + $newMemberData['url'], + $newMemberData['action'] + ) + ); + + VerifyUser::where('email', $currentEmail) + ->where('type', VerifyUser::TYPE_CANCEL_EMAIL) + ->delete(); + + $verify = new VerifyUser(); + $verify->code = $codeCurrentEmail; + $verify->email = $currentEmail; + $verify->type = VerifyUser::TYPE_CANCEL_EMAIL; $verify->created_at = now(); $verify->save(); // new email $codeNewEmail = Str::random(6); - $urlNewEmail = $url . '/change-email/confirm?code=' . $codeNewEmail . '&email=' . urlencode($newEmail); + $urlNewEmail = ( + $url . + '/change-email/confirm?code=' . + $codeNewEmail . + '&email=' . + urlencode($newEmail) + ); + $newMemberData = [ - 'title' => 'You recently updated your email', + 'title' => 'You recently updated your email', 'content' => 'You recently requested to update your email address with the Casper Association Portal. If this is correct, click the button below to confirm the change.
If you received this email in error, you can simply delete it', - 'url' => $urlNewEmail, - 'action' => 'confirm' + 'url' => $urlNewEmail, + 'action' => 'confirm' ]; - Mail::to($newEmail)->send(new UserConfirmEmail($newMemberData['title'], $newMemberData['content'], $newMemberData['url'], $newMemberData['action'])); - VerifyUser::where('email', $newEmail)->where('type', VerifyUser::TYPE_CONFIRM_EMAIL)->delete(); - $verify = new VerifyUser(); - $verify->email = $newEmail; - $verify->code = $codeNewEmail; - $verify->type = VerifyUser::TYPE_CONFIRM_EMAIL; + + Mail::to($newEmail)->send( + new UserConfirmEmail( + $newMemberData['title'], + $newMemberData['content'], + $newMemberData['url'], + $newMemberData['action'] + ) + ); + + VerifyUser::where('email', $newEmail) + ->where('type', VerifyUser::TYPE_CONFIRM_EMAIL) + ->delete(); + + $verify = new VerifyUser(); + $verify->email = $newEmail; + $verify->code = $codeNewEmail; + $verify->type = VerifyUser::TYPE_CONFIRM_EMAIL; $verify->created_at = now(); $verify->save(); } @@ -1161,104 +2551,159 @@ public function settingUser(Request $request) public function cancelChangeEmail(Request $request) { - $verify = VerifyUser::where('email', $request->email)->where('type', VerifyUser::TYPE_CANCEL_EMAIL) - ->where('code', $request->code)->first(); + $verify = VerifyUser::where('email', $request->email) + ->where('type', VerifyUser::TYPE_CANCEL_EMAIL) + ->where('code', $request->code) + ->first(); + if ($verify) { $user = User::where('email', $request->email)->first(); + if ($user) { $user->new_email = null; $user->save(); $verify->delete(); - VerifyUser::where('email', $user->new_email)->where('type', VerifyUser::TYPE_CONFIRM_EMAIL)->delete(); + + VerifyUser::where('email', $user->new_email) + ->where('type', VerifyUser::TYPE_CONFIRM_EMAIL) + ->delete(); + return $this->successResponse($user); } - return $this->errorResponse(__('Fail cancel change email'), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __('Fail cancel change email'), + Response::HTTP_BAD_REQUEST + ); } - return $this->errorResponse(__('Fail cancel change email'), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __('Fail cancel change email'), + Response::HTTP_BAD_REQUEST + ); } public function confirmChangeEmail(Request $request) { $verify = VerifyUser::where('email', $request->email) - ->where('type', VerifyUser::TYPE_CONFIRM_EMAIL) - ->where('code', $request->code) - ->first(); + ->where('type', VerifyUser::TYPE_CONFIRM_EMAIL) + ->where('code', $request->code) + ->first(); + if ($verify) { $user = User::where('new_email', $request->email)->first(); + if ($user) { - VerifyUser::where('email', $user->email)->where('type', VerifyUser::TYPE_CANCEL_EMAIL)->delete(); + VerifyUser::where('email', $user->email) + ->where('type', VerifyUser::TYPE_CANCEL_EMAIL) + ->delete(); + $user->new_email = null; - $user->email = $request->email; + $user->email = $request->email; $user->save(); $verify->delete(); return $this->successResponse($user); } - return $this->errorResponse(__('Fail confirm change email'), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __('Fail confirm change email'), + Response::HTTP_BAD_REQUEST + ); } - return $this->errorResponse(__('Fail confirm change email'), Response::HTTP_BAD_REQUEST); + return $this->errorResponse( + __('Fail confirm change email'), + Response::HTTP_BAD_REQUEST + ); } public function checkLogin2FA(Request $request) { - $user = auth()->user(); + $user = auth()->user(); $verify = VerifyUser::where('email', $user->email) - ->where('type', VerifyUser::TYPE_LOGIN_TWO_FA) - ->where('code', $request->code) - ->first(); + ->where('type', VerifyUser::TYPE_LOGIN_TWO_FA) + ->where('code', $request->code) + ->first(); + if ($verify) { $verify->delete(); $user->twoFA_login_active = 0; $user->save(); return $this->metaSuccess(); } - return $this->errorResponse(__('Fail check twoFA code'), Response::HTTP_BAD_REQUEST); + + return $this->errorResponse( + __('Fail check twoFA code'), + Response::HTTP_BAD_REQUEST + ); } public function resend2FA() { $user = auth()->user(); + if ($user->twoFA_login == 1) { - VerifyUser::where('email', $user->email)->where('type', VerifyUser::TYPE_LOGIN_TWO_FA)->delete(); - $code = Str::random(6); - $verify = new VerifyUser(); - $verify->email = $user->email; - $verify->type = VerifyUser::TYPE_LOGIN_TWO_FA; - $verify->code = $code; + VerifyUser::where('email', $user->email) + ->where('type', VerifyUser::TYPE_LOGIN_TWO_FA) + ->delete(); + + $code = Str::random(6); + $verify = new VerifyUser(); + $verify->email = $user->email; + $verify->type = VerifyUser::TYPE_LOGIN_TWO_FA; + $verify->code = $code; $verify->created_at = now(); $verify->save(); Mail::to($user)->send(new LoginTwoFA($code)); return $this->metaSuccess(); } - return $this->errorResponse(__('Please enable 2Fa setting'), Response::HTTP_BAD_REQUEST); + + return $this->errorResponse( + __('Please enable 2Fa setting'), + Response::HTTP_BAD_REQUEST + ); } public function getLockRules() { $user = auth()->user(); - $ruleKycNotVerify = LockRules::where('type', 'kyc_not_verify')->where('is_lock', 1) - ->orderBy('id', 'ASC')->select(['id', 'screen'])->get(); - $ruleKycNotVerify1 = array_map(function ($object) { - return $object->screen; - }, $ruleKycNotVerify->all()); - $ruleStatusIsPoor = LockRules::where('type', 'status_is_poor')->where('is_lock', 1) - ->orderBy('id', 'ASC')->select(['id', 'screen'])->get(); - $ruleStatusIsPoor1 = array_map(function ($object) { - return $object->screen; - }, $ruleStatusIsPoor->all()); + $ruleKycNotVerify = LockRules::where('type', 'kyc_not_verify') + ->where('is_lock', 1) + ->orderBy('id', 'ASC') + ->select(['id', 'screen']) + ->get(); + + $ruleKycNotVerify1 = array_map( + function ($object) { + return $object->screen; + }, + $ruleKycNotVerify->all() + ); + + $ruleStatusIsPoor = LockRules::where('type', 'status_is_poor') + ->where('is_lock', 1) + ->orderBy('id', 'ASC')->select(['id', 'screen']) + ->get(); + + $ruleStatusIsPoor1 = array_map( + function ($object) { + return $object->screen; + }, + $ruleStatusIsPoor->all() + ); $data = [ 'kyc_not_verify' => $ruleKycNotVerify1, 'status_is_poor' => $ruleStatusIsPoor1, - 'node_status' => $user->node_status + 'node_status' => $user->node_status ]; return $this->successResponse($data); } public function getListNodesBy(Request $request) { - $user = Auth::user(); - $addresses = UserAddress::where('user_id', $user->id)->orderBy('id', 'asc')->get(); + $user = Auth::user(); + $addresses = UserAddress::where('user_id', $user->id) + ->orderBy('id', 'asc') + ->get(); + return $this->successResponse([ 'addresses' => $addresses, ]); @@ -1266,6 +2711,38 @@ public function getListNodesBy(Request $request) public function getListNodes(Request $request) { + $user = auth()->user(); + $user_id = $user->id; + + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + $nodes = DB::select(" + SELECT + a.id AS user_id, a.pseudonym, + b.public_address_node, + c.stable, c.bad_mark, + d.blockchain_name, d.blockchain_desc + FROM users AS a + JOIN user_addresses AS b + ON a.id = b.user_id + JOIN all_node_data AS c + ON b.public_address_node = c.public_key + JOIN profile AS d + ON a.id = d.user_id + WHERE a.banned = 0 + AND a.id = $user_id + "); + info($nodes); + + + + $limit = $request->limit ?? 50; $nodes = UserAddress::select([ @@ -1289,38 +2766,80 @@ public function getListNodes(Request $request) public function infoDashboard() { - $user = auth()->user(); - $delegators = 0; - $stake_amount = 0; - $nodeInfo = NodeInfo::where('node_address', strtolower($user->public_address_node))->first(); + $user = auth()->user(); + $delegators = 0; + $stake_amount = 0; + $lower_address = strtolower($user->public_address_node); + + $nodeInfo = DB::select(" + SELECT * + FROM all_node_data + WHERE public_key = '$lower_address' + ORDER BY era_id DESC + LIMIT 1 + "); + $nodeInfo = $nodeInfo[0] ?? null; + if ($nodeInfo) { - $delegators = $nodeInfo->delegators_count; - $stake_amount = $nodeInfo->total_staked_amount; + $delegators = $nodeInfo->bid_delegators_count; + $stake_amount = $nodeInfo->bid_total_staked_amount; } + $totalPin = DiscussionPin::where('user_id', $user->id)->count(); + $response['totalNewDiscusstion'] = $user->new_threads; $response['totalPinDiscusstion'] = $totalPin; - $response['rank'] = $user->rank; - $response['delegators'] = $delegators; - $response['stake_amount'] = $stake_amount; + $response['rank'] = $user->rank; + $response['delegators'] = $delegators; + $response['stake_amount'] = $stake_amount; return $this->successResponse($response); } public function getEarningByNode($node) { - $node = strtolower($node); - $user = User::where('public_address_node', $node)->first(); - $nodeInfo = NodeInfo::where('node_address', $node)->first(); - $mbs = NodeInfo::max('mbs'); + $node = strtolower($node); + $user = User::where('public_address_node', $node)->first(); + + $nodeInfo = DB::select(" + SELECT * + FROM all_node_data + WHERE public_key = '$node' + ORDER BY era_id DESC + LIMIT 1 + "); + $nodeInfo = $nodeInfo[0] ?? null; + + // Calc earning + $one_day_ago = Carbon::now('UTC')->subHours(24); + $daily_earning = DB::select(" + SELECT bid_self_staked_amount + FROM all_node_data + WHERE public_key = '$node' + AND created_at < '$one_day_ago' + ORDER BY era_id DESC + LIMIT 1 + "); + $daily_earning = $daily_earning[0]->bid_self_staked_amount ?? 0; + $daily_earning = $nodeInfo->bid_self_staked_amount - $daily_earning; + $daily_earning = $daily_earning < 0 ? 0 : $daily_earning; + + $mbs = DB::select(" + SELECT mbs + FROM mbs + ORDER BY era_id DESC + LIMIT 1 + "); + $mbs = (int)($mbs[0]->mbs ?? 0); + if ($user && $nodeInfo) { return $this->successResponse([ - 'daily_earning' => $nodeInfo->daily_earning, - 'total_earning' => $nodeInfo->total_earning, - 'mbs' => $mbs, + 'daily_earning' => $daily_earning, + 'total_earning' => $daily_earning, + 'mbs' => $mbs, ]); } else { return $this->successResponse([ - 'mbs' => $mbs, + 'mbs' => $mbs, ]); } } @@ -1329,19 +2848,23 @@ public function getChartEarningByNode($node) { $node = strtolower($node); $user = User::where('public_address_node', $node)->first(); + if ($user) { - $nodeHelper = new NodeHelper(); - $result_day = $nodeHelper->getValidatorRewards($node, 'day'); - $result_week = $nodeHelper->getValidatorRewards($node, 'week'); - $result_month = $nodeHelper->getValidatorRewards($node, 'month'); - $result_year = $nodeHelper->getValidatorRewards($node, 'year'); + $nodeHelper = new NodeHelper(); + $result_day = $nodeHelper->getValidatorRewards($node, 'day'); + $result_week = $nodeHelper->getValidatorRewards($node, 'week'); + $result_month = $nodeHelper->getValidatorRewards($node, 'month'); + $result_year = $nodeHelper->getValidatorRewards($node, 'year'); + return $this->successResponse([ - 'day' => $result_day, - 'week' => $result_week, - 'month' => $result_month, - 'year' => $result_year, + 'day' => $result_day, + 'week' => $result_week, + 'month' => $result_month, + 'year' => $result_year, ]); - } else return $this->successResponse(null); + } else { + return $this->successResponse(null); + } } public function getMembershipFile() diff --git a/app/Services/NodeHelper.php b/app/Services/NodeHelper.php index f85882b9..276ccfef 100644 --- a/app/Services/NodeHelper.php +++ b/app/Services/NodeHelper.php @@ -39,12 +39,13 @@ public function __construct() public function decodePeers($__peers) { $decoded_peers = array(); - if($__peers && gettype($__peers) == 'array') { - foreach($__peers as $__peer) { + + if ($__peers && gettype($__peers) == 'array') { + foreach ($__peers as $__peer) { $address = $__peer['address'] ?? ''; $address = explode(':', $address)[0]; - if($address) { + if ($address) { $decoded_peers[] = $address; } } @@ -54,7 +55,7 @@ public function decodePeers($__peers) public function retrieveGlobalUptime($this_era_id) { - $total_data = array(); + $total_data = array(); $event_store_url = 'https://event-store-api-clarity-mainnet.make.services/relative-average-validator-performances?limit=100&page=1&era_id='.(string)($this_era_id - 1); @@ -64,7 +65,7 @@ public function retrieveGlobalUptime($this_era_id) curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); $json = curl_exec($ch); - if(curl_errno($ch)) { + if (curl_errno($ch)) { return array(); } @@ -80,19 +81,19 @@ public function retrieveGlobalUptime($this_era_id) // update total data object - $data = $object->data ?? array(); + $data = $object->data ?? array(); $total_data = array_merge($total_data, $data); // iterate through remaining pages - for($i = 0; $i < $page_count; $i++) { - if($i != 0) { + for ($i = 0; $i < $page_count; $i++) { + if ($i != 0) { $j = $i + 1; $event_store_url = 'https://event-store-api-clarity-mainnet.make.services/relative-average-validator-performances?limit=100&page='.$j.'&era_id='.(string)($this_era_id - 1); curl_setopt($ch, CURLOPT_URL, $event_store_url); $json = curl_exec($ch); - if(curl_errno($ch)) { + if (curl_errno($ch)) { continue; } @@ -104,7 +105,7 @@ public function retrieveGlobalUptime($this_era_id) // update total data object - $data = $object->data ?? array(); + $data = $object->data ?? array(); $total_data = array_merge($total_data, $data); } sleep(1); @@ -118,8 +119,8 @@ public function retrieveGlobalUptime($this_era_id) public function discoverPeers() { $port8888_responses = array(); - $http_protocol = 'http://'; - $status_port = ':8888/status'; + $http_protocol = 'http://'; + $status_port = ':8888/status'; // Get peers from trusted node, port 8888 @@ -132,13 +133,13 @@ public function discoverPeers() // try once with main NODE_IP $json = curl_exec($curl); - if(curl_errno($curl) && getenv('BACKUP_NODE_IP')) { + if (curl_errno($curl) && getenv('BACKUP_NODE_IP')) { curl_setopt($curl, CURLOPT_URL, $http_protocol.getenv('BACKUP_NODE_IP').$status_port); // try twice with main BACKUP_NODE_IP $json = curl_exec($curl); - if(curl_errno($curl)) { + if (curl_errno($curl)) { return array(); } } @@ -160,7 +161,7 @@ public function discoverPeers() $peers = array_unique($peers); $peers = array_values($peers); - if(!$peers || empty($peers)) { + if (!$peers || empty($peers)) { return array(); } @@ -170,23 +171,23 @@ public function discoverPeers() // divide up chunks for multi curl handler - if(count($peers) >= 20) { + if (count($peers) >= 20) { $divided = (int)(count($peers) / 20); } else { $divided = 1; } - $remainder = count($peers) % 20; - $real_index = 0; + $remainder = count($peers) % 20; + $real_index = 0; info('Requesting peers.. Total: '.count($peers)); // multi curl handler each full chunk - for($chunk = 0; $chunk < $divided; $chunk++) { + for ($chunk = 0; $chunk < $divided; $chunk++) { $mh = curl_multi_init(); $ch = array(); - for($i = 0; $i < 20; $i++) { + for ($i = 0; $i < 20; $i++) { $ch[$i] = curl_init(); curl_setopt($ch[$i], CURLOPT_URL, $http_protocol.$peers[$real_index].$status_port); curl_setopt($ch[$i], CURLOPT_HEADER, 0); @@ -203,16 +204,16 @@ public function discoverPeers() do { $status = curl_multi_exec($mh, $active); - if($active) { + if ($active) { curl_multi_select($mh); } - } while($active && $status == CURLM_OK); + } while ($active && $status == CURLM_OK); - for($i = 0; $i < 20; $i++) { + for ($i = 0; $i < 20; $i++) { curl_multi_remove_handle($mh, $ch[$i]); } - foreach($ch as $req) { + foreach ($ch as $req) { $content = curl_multi_getcontent($req); if($content) { @@ -256,7 +257,7 @@ public function discoverPeers() } } while ($active && $status == CURLM_OK); - for($i = 0; $i < $remainder; $i++) { + for ($i = 0; $i < $remainder; $i++) { curl_multi_remove_handle($mh, $ch[$i]); } @@ -281,12 +282,12 @@ public function discoverPeers() public function getValidAddresses() { - $curl = curl_init(); + $curl = curl_init(); $json_data = [ - 'id' => (int) time(), + 'id' => (int) time(), 'jsonrpc' => '2.0', - 'method' => 'state_get_auction_info', - 'params' => array() + 'method' => 'state_get_auction_info', + 'params' => array() ]; curl_setopt($curl, CURLOPT_URL, 'http://' . getenv('NODE_IP') . ':7777/rpc'); @@ -303,23 +304,31 @@ public function getValidAddresses() // parse response for bids from NODE_IP $response = curl_exec($curl); - if (curl_errno($curl) && getenv('BACKUP_NODE_IP')) { - curl_setopt($curl, CURLOPT_URL, 'http://' . getenv('BACKUP_NODE_IP') . ':7777/rpc'); + if ( + curl_errno($curl) && + getenv('BACKUP_NODE_IP') + ) { + curl_setopt( + $curl, + CURLOPT_URL, + 'http://' . getenv('BACKUP_NODE_IP') . ':7777/rpc' + ); // try twice with BACKUP_NODE_IP $response = curl_exec($curl); } curl_close($curl); - $decodedResponse = json_decode($response, true); - $auction_state = $decodedResponse['result']['auction_state'] ?? []; - $bids = $auction_state['bids'] ?? []; + $decoded_response = json_decode($response, true); + $auction_state = $decoded_response['result']['auction_state'] ?? []; + $bids = $auction_state['bids'] ?? []; $addresses = []; + if ($bids) { - foreach($bids as $bid) { + foreach ($bids as $bid) { if (isset($bid['public_key']) && $bid['public_key']) { - $public_key = strtolower($bid['public_key']); + $public_key = strtolower($bid['public_key']); $addresses[] = $public_key; } } @@ -330,16 +339,17 @@ public function getValidAddresses() public function getValidatorStanding() { // get node ips from peers - $port8888_responses = $this->discoverPeers(); + // $port8888_responses = $this->discoverPeers(); + $port8888_responses = array(); // get auction state from trusted node RPC $curl = curl_init(); $json_data = array( - 'id' => (int) time(), + 'id' => (int)time(), 'jsonrpc' => '2.0', - 'method' => 'state_get_auction_info', - 'params' => array() + 'method' => 'state_get_auction_info', + 'params' => array() ); curl_setopt($curl, CURLOPT_URL, 'http://' . getenv('NODE_IP') . ':7777/rpc'); @@ -354,202 +364,453 @@ public function getValidatorStanding() )); // parse response for bids - $response = curl_exec($curl); + $auction_response = curl_exec($curl); if (curl_errno($curl) && getenv('BACKUP_NODE_IP')) { curl_setopt($curl, CURLOPT_URL, 'http://' . getenv('BACKUP_NODE_IP') . ':7777/rpc'); // try twice with BACKUP_NODE_IP - $response = curl_exec($curl); + $auction_response = curl_exec($curl); } curl_close($curl); - $decodedResponse = json_decode($response, true); - $auction_state = $decodedResponse['result']['auction_state'] ?? array(); - $bids = $auction_state['bids'] ?? array(); + + // very large object. aprx 10MB + $decoded_response = json_decode($auction_response, true); + $auction_state = $decoded_response['result']['auction_state'] ?? array(); + $bids = $auction_state['bids'] ?? array(); // get era ID - $era_id = (int)($auction_state['era_validators'][0]['era_id'] ?? 0); + $era_validators = $auction_state['era_validators'] ?? array(); + $current_era_validators = $era_validators[0] ?? array(); + $next_era_validators = $era_validators[1] ?? array(); + $current_era_id = (int)($current_era_validators['era_id'] ?? 0); + $next_era_id = (int)($next_era_validators['era_id'] ?? 0); + + // loop current era + $current_validator_weights = $current_era_validators['validator_weights'] ?? array(); + + foreach ($current_validator_weights as $v) { + $public_key = $v['public_key'] ?? ''; + $weight = (int)($v['weight'] / 1000000000 ?? 0); + + $check = DB::select(" + SELECT public_key, era_id + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id = $current_era_id + "); + $check = $check[0] ?? null; + + if (!$check) { + DB::table('all_node_data')->insert( + array( + 'public_key' => $public_key, + 'era_id' => $current_era_id, + 'current_era_weight' => $weight, + 'in_curent_era' => 1, + 'created_at' => Carbon::now('UTC') + ) + ); + } + } + + // loop next era + $next_validator_weights = $next_era_validators['validator_weights'] ?? array(); + + foreach ($next_validator_weights as $v) { + $public_key = $v['public_key'] ?? ''; + $weight = (int)($v['weight'] / 1000000000 ?? 0); + + $check = DB::select(" + SELECT public_key, era_id + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id = $current_era_id + "); + $check = $check[0] ?? null; + + if (!$check) { + DB::table('all_node_data')->insert( + array( + 'public_key' => $public_key, + 'era_id' => $current_era_id, + 'next_era_weight' => $weight, + 'in_next_era' => 1 + ) + ); + } else { + DB::table('all_node_data') + ->where('public_key', $public_key) + ->where('era_id', $current_era_id) + ->update( + array( + 'next_era_weight' => $weight, + 'in_next_era' => 1 + ) + ); + } + } // set MBS array. minimum bid slot amount $MBS_arr = array(); - // set default object - $global_validator_standing = array( - "global_block_height" => 0, - "global_build_version" => '1.0.0', - "global_chainspec_name" => 'casper', - "validator_standing" => array() - ); + // get global uptimes from MAKE + $global_uptime = $this->retrieveGlobalUptime($current_era_id); + + // loop auction era + foreach ($bids as $b) { + $public_key = strtolower($b['public_key'] ?? 'nill'); + $bid = $b['bid'] ?? array(); + + // get self + $self_staked_amount = (int)($bid['staked_amount'] ?? 0); + $delegation_rate = (int)($bid['delegation_rate'] ?? 0); + $bid_inactive = (int)($bid['inactive'] ?? false); + + // calculate total stake, delegators + self stake + $delegators = (array)($bid['delegators'] ?? array()); + $delegators_count = count($delegators); + $delegators_staked_amount = 0; - // check each bid validator against existing platform validators - if($bids) { - // get global uptimes from MAKE - $global_uptime = $this->retrieveGlobalUptime($era_id); + foreach ($delegators as $delegator) { + $delegators_staked_amount += (int)($delegator['staked_amount'] ?? 0); + } - foreach($bids as $bid) { - $public_key = strtolower($bid['public_key'] ?? 'nill'); - $node_info = UserAddress::where('public_address_node', $public_key)->first(); + // convert and calculate stake amounts + $delegators_staked_amount = (int)($delegators_staked_amount / 1000000000); + $self_staked_amount = (int)($self_staked_amount / 1000000000); + $total_staked_amount = $delegators_staked_amount + $self_staked_amount; - // parse bid - $b = $bid['bid'] ?? array(); + // append to MBS array and pluck 100th place later + $MBS_arr[$public_key] = $total_staked_amount; - // get self stake amount - $self_staked_amount = (int)($b['staked_amount'] ?? 0); + // get node uptime from MAKE object + $uptime = 0; - // calculate total stake, delegators + self stake - $delegators = (array)($b['delegators'] ?? array()); - $delegators_count = count($delegators); - $total_staked_amount = 0 + $self_staked_amount; + foreach ($global_uptime as $uptime_array) { + $fvid = strtolower($uptime_array->public_key ?? ''); - foreach($delegators as $delegator) { - $staked_amount = (int)($delegator['staked_amount'] ?? 0); - $total_staked_amount += $staked_amount; + if($fvid == $public_key) { + $uptime = (float)($uptime_array->average_score ?? 0); + break; } + } - $total_staked_amount = $total_staked_amount / 1000000000; - $self_staked_amount = $self_staked_amount / 1000000000; - - // append to MBS array and pluck 100th place later - $MBS_arr[$public_key] = $total_staked_amount; - - // node exists on platform, fetch/save info - if($node_info) { - // get delegation rate - $delegation_rate = (float)($b['delegation_rate'] ?? 0); - - // get active status (equivocation check) - $inactive = (bool)($b['inactive'] ?? false); - - // save current stake amount to daily earnings table - $earning = new DailyEarning(); - $earning->node_address = $public_key; - $earning->self_staked_amount = (int)$self_staked_amount; - $earning->created_at = Carbon::now('UTC'); - $earning->save(); - - // get difference between current self stake and yesterdays self stake - $get_earning = DailyEarning::where('node_address', $public_key) - ->where('created_at', '>', Carbon::now('UTC')->subHours(24)) - ->orderBy('created_at', 'asc') - ->first(); - $yesterdays_self_staked_amount = (float)($get_earning->self_staked_amount ?? 0); - $daily_earning = $self_staked_amount - $yesterdays_self_staked_amount; - - // get node uptime from MAKE object - $uptime = 0; - - foreach($global_uptime as $uptime_array) { - $fvid = strtolower($uptime_array->public_key ?? ''); - - if($fvid == $public_key) { - $uptime = (float)($uptime_array->average_score ?? 1); - break; - } - } + // record auction bid data for this node + $check = DB::select(" + SELECT public_key, era_id + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id = $current_era_id + "); + $check = $check[0] ?? null; + + if (!$check) { + DB::table('all_node_data')->insert( + array( + 'public_key' => $public_key, + 'era_id' => $current_era_id, + 'uptime' => $uptime, + 'in_auction' => 1, + 'bid_delegators_count' => $delegators_count, + 'bid_delegation_rate' => $delegation_rate, + 'bid_inactive' => $bid_inactive, + 'bid_self_staked_amount' => $self_staked_amount, + 'bid_delegators_staked_amount' => $delegators_staked_amount, + 'bid_total_staked_amount' => $total_staked_amount + ) + ); + } else { + DB::table('all_node_data') + ->where('public_key', $public_key) + ->where('era_id', $current_era_id) + ->update( + array( + 'uptime' => $uptime, + 'in_auction' => 1, + 'bid_delegators_count' => $delegators_count, + 'bid_delegation_rate' => $delegation_rate, + 'bid_inactive' => $bid_inactive, + 'bid_self_staked_amount' => $self_staked_amount, + 'bid_delegators_staked_amount' => $delegators_staked_amount, + 'bid_total_staked_amount' => $total_staked_amount + ) + ); + } - $float_uptime = (float)($uptime / 100.0); - - // build individual validator_standing object - $global_validator_standing - ['validator_standing'] - [$public_key] - ["delegators_count"] = $delegators_count; - - $global_validator_standing - ['validator_standing'] - [$public_key] - ["total_staked_amount"] = $total_staked_amount; - - $global_validator_standing - ['validator_standing'] - [$public_key] - ["self_staked_amount"] = $self_staked_amount; - - $global_validator_standing - ['validator_standing'] - [$public_key] - ["delegation_rate"] = $delegation_rate; - - $global_validator_standing - ['validator_standing'] - [$public_key] - ["uptime"] = $float_uptime; - - $global_validator_standing - ['validator_standing'] - [$public_key] - ["update_responsiveness"] = 1; - - $global_validator_standing - ['validator_standing'] - [$public_key] - ["daily_earning"] = $daily_earning; - - $global_validator_standing - ['validator_standing'] - [$public_key] - ["inactive"] = $inactive; - - - // look for existing peer by public key for port 8888 data - foreach($port8888_responses as $port8888data) { - $our_public_signing_key = strtolower($port8888data['our_public_signing_key'] ?? ''); - - if($our_public_signing_key == $public_key) { - // found in peers - $peer_count = (int)($port8888data['peer_count'] ?? 0); - $block_height = (int)($port8888data['last_added_block_info']['height'] ?? 0); - $build_version = $port8888data['build_version'] ?? '1.0.0'; - $chainspec_name = $port8888data['chainspec_name'] ?? 'casper'; - - if($block_height > $global_validator_standing["global_block_height"]) { - $global_validator_standing["global_block_height"] = $block_height; - } - - if($build_version > $global_validator_standing["global_build_version"]) { - $global_validator_standing["global_build_version"] = $build_version; - } - - - // apply to global_validator_standing - $global_validator_standing - ['validator_standing'] - [$public_key] - ["block_height"] = $block_height; - - $global_validator_standing - ['validator_standing'] - [$public_key] - ["build_version"] = $build_version; - - $global_validator_standing - ['validator_standing'] - [$public_key] - ["chainspec_name"] = $chainspec_name; - - $global_validator_standing - ['validator_standing'] - [$public_key] - ["peer_count"] = $peer_count; - - break; - } + // save current stake amount to daily earnings table + $earning = new DailyEarning(); + $earning->node_address = $public_key; + $earning->self_staked_amount = (int)$self_staked_amount; + $earning->created_at = Carbon::now('UTC'); + $earning->save(); + + // get difference between current self stake and yesterdays self stake + $get_earning = DailyEarning::where('node_address', $public_key) + ->where('created_at', '>', Carbon::now('UTC')->subHours(24)) + ->orderBy('created_at', 'asc') + ->first(); + + $yesterdays_self_staked_amount = (int)($get_earning->self_staked_amount ?? 0); + $daily_earning = $self_staked_amount - $yesterdays_self_staked_amount; + + // look for existing peer by public key for port 8888 data + foreach ($port8888_responses as $port8888data) { + $our_public_signing_key = strtolower($port8888data['our_public_signing_key'] ?? ''); + + if ($our_public_signing_key == $public_key) { + // found in peers + $peer_count = (int)($port8888data['peer_count'] ?? 0); + $era_id = (int)($port8888data['last_added_block_info']['era_id'] ?? 0); + $block_height = (int)($port8888data['last_added_block_info']['height'] ?? 0); + $build_version = $port8888data['build_version'] ?? '1.0.0'; + $build_version = explode('-', $build_version)[0]; + $chainspec_name = $port8888data['chainspec_name'] ?? 'casper'; + $next_upgrade = $port8888data['next_upgrade']['protocol_version'] ?? ''; + + // record port 8888 data if available + $check = DB::select(" + SELECT public_key, era_id + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id = $current_era_id + "); + $check = $check[0] ?? null; + + if (!$check) { + DB::table('all_node_data')->insert( + array( + 'public_key' => $public_key, + 'era_id' => $current_era_id, + 'port8888_peers' => $peer_count, + 'port8888_era_id' => $era_id, + 'port8888_block_height' => $block_height, + 'port8888_build_version' => $build_version, + 'port8888_next_upgrade' => $next_upgrade + ) + ); + } else { + DB::table('all_node_data') + ->where('public_key', $public_key) + ->where('era_id', $current_era_id) + ->update( + array( + 'port8888_peers' => $peer_count, + 'port8888_era_id' => $era_id, + 'port8888_block_height' => $block_height, + 'port8888_build_version' => $build_version, + 'port8888_next_upgrade' => $next_upgrade + ) + ); } + + break; } } } // find MBS rsort($MBS_arr); - $MBS = null; - if (count($MBS_arr) > 0) $MBS = $MBS_arr[99] ?? $MBS_arr[count($MBS_arr) - 1]; - $global_validator_standing['MBS'] = $MBS; + $MBS = 0; + + if (count($MBS_arr) > 0) { + $MBS = (float)($MBS_arr[99] ?? $MBS_arr[count($MBS_arr) - 1]); + } + + // save MBS in new table by current_era + $check = DB::select(" + SELECT mbs + FROM mbs + WHERE era_id = $current_era_id + "); + $check = $check[0] ?? null; + + if (!$check) { + DB::table('mbs')->insert( + array( + 'era_id' => $current_era_id, + 'mbs' => $MBS, + 'created_at' => Carbon::now('UTC') + ) + ); + } else { + DB::table('mbs') + ->where('era_id', $current_era_id) + ->update( + array( + 'mbs' => $MBS, + 'updated_at' => Carbon::now('UTC') + ) + ); + } + + // Get settings for stable eras requirement + $eras_to_be_stable = DB::select(" + SELECT value + FROM settings + WHERE name = 'eras_to_be_stable' + "); + $a = (array)($eras_to_be_stable[0] ?? array()); + $eras_to_be_stable = (int)($a['value'] ?? 0); + + // Create setting if not exist + if (!$eras_to_be_stable) { + $eras_to_be_stable = 100; + + DB::table('settings')->insert( + array( + 'name' => 'eras_to_be_stable', + 'value' => '100' + ) + ); + } + + // Discover black marks - validator has sufficient stake, but failed to win slot. + $eras_look_back = DB::select(" + SELECT value + FROM settings + WHERE name = 'eras_look_back' + "); + $a = (array)($eras_look_back[0] ?? array()); + $eras_look_back = (int)($a['value'] ?? 0); + + // Create setting if not exist + if (!$eras_look_back) { + $eras_look_back = 360; + + DB::table('settings')->insert( + array( + 'name' => 'eras_look_back', + 'value' => '360' + ) + ); + } + + $previous_era_id = $current_era_id - 1; + $previous_mbs = DB::select(" + SELECT mbs + FROM mbs + WHERE era_id = $previous_era_id + "); + $previous_mbs = (float)($previous_mbs[0]['mbs'] ?? 0); + + foreach ($bids as $b) { + $public_key = strtolower($b['public_key'] ?? 'nill'); + + // last era + $previous_stake = DB::select(" + SELECT bid_total_staked_amount + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id = $previous_era_id + "); + $a = (array)($previous_stake[0] ?? array()); + $previous_stake = (int)($a['bid_total_staked_amount'] ?? 0); + + // current era + $next_era_weight = DB::select(" + SELECT next_era_weight + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id = $current_era_id + "); + $a = (array)($next_era_weight[0] ?? array()); + $next_era_weight = (int)($a['next_era_weight'] ?? 0); + + // Calculate historical_performance from past $eras_look_back eras + $missed = 0; + $in_curent_eras = DB::select(" + SELECT in_curent_era + FROM all_node_data + WHERE public_key = '$public_key' + ORDER BY era_id DESC + LIMIT $eras_look_back + "); + + $in_curent_eras = $in_curent_eras ? $in_curent_eras : array(); + $window = $in_curent_eras ? count($in_curent_eras) : 0; + + foreach ($in_curent_eras as $c) { + $a = (array)$c; + $in = (bool)($a['in_curent_era'] ?? 0); + + if (!$in) { + $missed += 1; + } + } + + // get node uptime from MAKE object + $uptime = 0; + + foreach ($global_uptime as $uptime_array) { + $fvid = strtolower($uptime_array->public_key ?? ''); + + if($fvid == $public_key) { + $uptime = (float)($uptime_array->average_score ?? 0); + break; + } + } + + $historical_performance = ($uptime * ($window - $missed)) / $window; + $past_era = $current_era_id - $eras_to_be_stable; + $bad_mark = 0; + + if ($past_era < 0) { + $past_era = 0; + } + + if ( + $previous_stake > $previous_mbs && + !$next_era_weight + ) { + // black mark + $bad_mark = 1; + } + + // Update stability + $unstable = DB::select(" + SELECT public_key + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id > $past_era + AND ( + bad_mark = 1 OR + bid_inactive = 1 + ) + "); + + $stability_count = DB::select(" + SELECT public_key + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id > $past_era + "); + + $unstable = (bool)($unstable); + $stable = (int)(!$unstable); + $stability_count = $stability_count ? count($stability_count) : 0; + + if ($stability_count < $eras_to_be_stable) { + $stable = 0; + } + + DB::table('all_node_data') + ->where('public_key', $public_key) + ->where('era_id', $current_era_id) + ->update( + array( + 'bad_mark' => $bad_mark, + 'stable' => $stable, + 'historical_performance' => $historical_performance + ) + ); + } // DailyEarning garbage cleanup DailyEarning::where('created_at', '<', Carbon::now('UTC')->subDays(90))->delete(); - return $global_validator_standing; + return true; } public function getTotalRewards($validatorId) @@ -560,122 +821,14 @@ public function getTotalRewards($validatorId) return $response->json(); } - public function updateStats() - { - $data = $this->getValidatorStanding(); - $mbs = $data['MBS'] ?? 0; - $peers = $data['peers'] ?? 0; - $users = User::with('addresses')->whereNotNull('public_address_node')->get(); - $validator_standing = $data['validator_standing'] ?? null; - $setting = Setting::where('name', 'peers')->first(); - - if (!$setting) { - $setting = new Setting; - $setting->name = 'peers'; - $setting->value = ''; - $setting->save(); - } - - $setting->value = $peers; - $setting->save(); - - if ($validator_standing) { - // Refresh Validator Standing - foreach ($validator_standing as $key => $value) { - $validator_standing[strtolower($key)] = $value; - } - - foreach ($users as $user) { - if (isset($user->addresses) && count($user->addresses) > 0) { - $addresses = $user->addresses; - $userAddress = strtolower($user->public_address_node); - foreach ($addresses as $address) { - $validatorid = strtolower($address->public_address_node); - - if (isset($validator_standing[$validatorid])) { - $info = $validator_standing[$validatorid]; - $fee = (float) $info['delegation_rate']; - - if ($userAddress == $validatorid) { - $user->pending_node = 0; - $user->validator_fee = round($fee, 2); - $user->save(); - } - - $address->pending_node = 0; - $address->validator_fee = round($fee, 2); - $address->save(); - - $totalRewards = $this->getTotalRewards($validatorid); - - $build_version = $info['build_version'] ?? null; - - if ($build_version) { - $build_version = explode('-', $build_version); - $build_version = $build_version[0]; - } - - if( - isset($info['block_height']) && - isset($info['peer_count']) - ) { - $is_open_port = 1; - } else { - $is_open_port = 0; - } - - $inactive = (bool)($info['inactive'] ?? false); - $inactive = $inactive ? 1 : 0; - - NodeInfo::updateOrCreate( - [ - 'node_address' => $validatorid - ], - [ - 'delegators_count' => $info['delegators_count'] ?? 0, - 'total_staked_amount' => $info['total_staked_amount'], - 'delegation_rate' => $info['delegation_rate'], - 'daily_earning' => $info['daily_earning'] ?? 0, - 'self_staked_amount' => $info['self_staked_amount'] ?? 0, - 'total_earning' => isset($totalRewards['data']) && $totalRewards['data'] > 0 ? $totalRewards['data'] / 1000000000 : 0, - 'is_open_port' => $is_open_port, - 'mbs' => $mbs, - 'update_responsiveness' => isset($info['update_responsiveness']) ? $info['update_responsiveness'] * 100 : 0, - 'uptime' => isset($info['uptime']) ? $info['uptime'] * 100 : 0, - 'block_height' => $info['block_height'] ?? 0, - 'peers' => $info['peer_count'] ?? 0, - 'inactive' => $inactive - ] - ); - - Node::updateOrCreate( - [ - 'node_address' => $validatorid - ], - [ - 'block_height' => $info['block_height'] ?? null, - 'protocol_version' => $build_version, - 'update_responsiveness' => isset($info['update_responsiveness']) ? $info['update_responsiveness'] * 100 : null, - 'uptime' => isset($info['uptime']) ? $info['uptime'] : null, - 'weight' => $info['daily_earnings'] ?? 0, - 'peers' => $info['peer_count'] ?? 0, - ] - ); - } - } - } - } - } - } - public function getValidatorRewards($validatorId, $_range) { - $nowtime = (int)time(); - $range = 0; - $days = 30; + $nowtime = (int)time(); + $range = 0; + $days = 30; $leap_year = (int)date('Y'); $leap_days = $leap_year % 4 == 0 ? 29 : 28; - $month = (int)date('m'); + $month = (int)date('m'); switch($month) { case 1: @@ -736,27 +889,27 @@ public function getValidatorRewards($validatorId, $_range) break; } - $timestamp = Carbon::createFromTimestamp($range, 'UTC')->toDateTimeString(); + $timestamp = Carbon::createFromTimestamp($range, 'UTC')->toDateTimeString(); $total_records = DailyEarning::where('node_address', $validatorId) ->where('created_at', '>', $timestamp) ->get(); - $new_array = array(); + $new_array = array(); $display_record_count = 100; - if($total_records) { - $modded = count($total_records) % $display_record_count; + if ($total_records) { + $modded = count($total_records) % $display_record_count; $numerator = count($total_records) - $modded; - $modulo = $numerator / $display_record_count; + $modulo = $numerator / $display_record_count; $new_array = array(); - $i = $modulo; + $i = $modulo; - if($modulo == 0) { + if ($modulo == 0) { $modulo = 1; } - foreach($total_records as $record) { - if($i % $modulo == 0) { + foreach ($total_records as $record) { + if ($i % $modulo == 0) { $new_array[(string)strtotime($record->created_at.' UTC')] = (string)$record->self_staked_amount; } $i++; diff --git a/database/migrations/2022_09_21_162427_create_all_node_data.php b/database/migrations/2022_09_21_162427_create_all_node_data.php new file mode 100644 index 00000000..dcbe515e --- /dev/null +++ b/database/migrations/2022_09_21_162427_create_all_node_data.php @@ -0,0 +1,71 @@ +id(); + $table->string('public_key'); + $table->integer('era_id'); + + // Make uptime + $table->float('uptime')->nullable(); + + // Calculated uptime over an abstract number of eras + $table->float('historical_performance')->default(0); + + // From auction object + $table->bigInteger('current_era_weight')->default(0); + $table->bigInteger('next_era_weight')->default(0); + + // Found in auction object + $table->tinyInteger('in_curent_era')->default(0); + $table->tinyInteger('in_next_era')->default(0); + $table->tinyInteger('in_auction')->default(0); + + // Should have won a slot in this era, but did not + $table->tinyInteger('bad_mark')->default(0); + + // Validator able to vote after $Settings->eras_to_be_stable of good history. + $table->tinyInteger('stable')->default(0); + + // Auction bid details + $table->integer('bid_delegators_count')->nullable(); + $table->bigInteger('bid_delegation_rate')->nullable(); + $table->tinyInteger('bid_inactive')->default(0); + $table->bigInteger('bid_self_staked_amount')->default(0); + $table->bigInteger('bid_delegators_staked_amount')->default(0); + $table->bigInteger('bid_total_staked_amount')->default(0); + + // the following fields come from port 8888 when/if discovered + $table->integer('port8888_peers')->nullable(); + $table->integer('port8888_era_id')->nullable(); + $table->integer('port8888_block_height')->nullable(); + $table->string('port8888_build_version')->nullable(); + $table->string('port8888_next_upgrade')->nullable(); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('all_node_data'); + } +} diff --git a/database/migrations/2022_09_21_204420_create_mbs.php b/database/migrations/2022_09_21_204420_create_mbs.php new file mode 100644 index 00000000..678160f6 --- /dev/null +++ b/database/migrations/2022_09_21_204420_create_mbs.php @@ -0,0 +1,33 @@ +id(); + $table->integer('era_id'); + $table->float('mbs')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('mbs'); + } +} diff --git a/routes/api.php b/routes/api.php index 568a8a83..dc5c7824 100644 --- a/routes/api.php +++ b/routes/api.php @@ -52,6 +52,19 @@ Route::post('/users/check-validator-address', [UserController::class, 'checkValidatorAddress']); Route::middleware(['auth:api'])->group(function () { Route::middleware(['user_banned'])->group(function () { + // New dashboard endpoint + Route::get('/users/get-dashboard', [UserController::class, 'getUserDashboard']); + + // New membership page endpoint + Route::get('/users/get-membership-page', [UserController::class, 'getMembershipPage']); + + // New Nodes page endpoint + Route::get('/users/get-nodes-page', [UserController::class, 'getNodesPage']); + + // New My Eras page endpoint + Route::get('/users/get-my-eras', [UserController::class, 'getMyEras']); + + Route::post('/users/verify-email', [AuthController::class, 'verifyEmail']); Route::post('/users/resend-verify-email', [AuthController::class, 'resendVerifyEmail']); Route::post('/users/change-email', [UserController::class, 'changeEmail']); @@ -105,6 +118,10 @@ }); Route::prefix('admin')->middleware(['role_admin'])->group(function () { + // New Nodes page endpoint + Route::get('/users/get-nodes-page', [AdminController::class, 'getNodesPage']); + + Route::get('/users', [AdminController::class, 'getUsers']); Route::get('/users/{id}', [AdminController::class, 'getUserDetail'])->where('id', '[0-9]+'); Route::get('/dashboard', [AdminController::class, 'infoDashboard']); From 92fcad65ca8c809ea7f546b217639c6f849408a9 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 5 Oct 2022 14:31:29 -0400 Subject: [PATCH 002/162] # User Earning API Validation --- app/Http/Controllers/Api/V1/UserController.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index c1622f5c..9279df7e 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -2811,7 +2811,7 @@ public function getEarningByNode($node) // Calc earning $one_day_ago = Carbon::now('UTC')->subHours(24); - $daily_earning = DB::select(" + $daily_earningObject = DB::select(" SELECT bid_self_staked_amount FROM all_node_data WHERE public_key = '$node' @@ -2819,10 +2819,12 @@ public function getEarningByNode($node) ORDER BY era_id DESC LIMIT 1 "); - $daily_earning = $daily_earning[0]->bid_self_staked_amount ?? 0; - $daily_earning = $nodeInfo->bid_self_staked_amount - $daily_earning; + $daily_earning = 0; + if ($daily_earningObject && count($daily_earningObject) > 0) $daily_earning = $daily_earningObject[0]->bid_self_staked_amount ?? 0; + if ($nodeInfo) $daily_earning = $nodeInfo->bid_self_staked_amount - $daily_earning; + else $daily_earning = -$daily_earning; $daily_earning = $daily_earning < 0 ? 0 : $daily_earning; - + $mbs = DB::select(" SELECT mbs FROM mbs From 94d9b8483b22ffb396216fc5e8397d863c687c8b Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 5 Oct 2022 15:30:32 -0400 Subject: [PATCH 003/162] node helper mbs fix --- app/Console/Commands/HistoricalData.php | 37 +++++++++++++++++ .../Controllers/Api/V1/AdminController.php | 40 ++++++++++--------- app/Services/NodeHelper.php | 2 +- 3 files changed, 59 insertions(+), 20 deletions(-) create mode 100644 app/Console/Commands/HistoricalData.php diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php new file mode 100644 index 00000000..4f54be6f --- /dev/null +++ b/app/Console/Commands/HistoricalData.php @@ -0,0 +1,37 @@ + ($request->quorum_rate_ballot ?? ''), - 'uptime_warning' => ($request->uptime_warning ?? ''), - 'uptime_probation' => ($request->uptime_probation ?? ''), - 'uptime_correction_time' => ($request->uptime_correction_time ?? ''), - 'uptime_calc_size' => ($request->uptime_calc_size ?? ''), - 'voting_eras_to_vote' => ($request->voting_eras_to_vote ?? ''), - 'voting_eras_since_redmark' => ($request->voting_eras_since_redmark ?? ''), - 'redmarks_revoke' => ($request->redmarks_revoke ?? ''), - 'responsiveness_warning' => ($request->responsiveness_warning ?? ''), - 'responsiveness_probation' => ($request->responsiveness_probation ?? '') + 'quorum_rate_ballot' => ($request->quorum_rate_ballot ?? null), + 'uptime_warning' => ($request->uptime_warning ?? null), + 'uptime_probation' => ($request->uptime_probation ?? null), + 'uptime_correction_time' => ($request->uptime_correction_time ?? null), + 'uptime_calc_size' => ($request->uptime_calc_size ?? null), + 'voting_eras_to_vote' => ($request->voting_eras_to_vote ?? null), + 'voting_eras_since_redmark' => ($request->voting_eras_since_redmark ?? null), + 'redmarks_revoke' => ($request->redmarks_revoke ?? null), + 'responsiveness_warning' => ($request->responsiveness_warning ?? null), + 'responsiveness_probation' => ($request->responsiveness_probation ?? null) ]; foreach ($items as $name => $value) { - $setting = Setting::where('name', $name)->first(); - - if ($setting) { - $setting->value = $value; - $setting->save(); - } else { - $setting = new Setting(); - $setting->value = $value; - $setting->save(); + if ($value !== null) { + $setting = Setting::where('name', $name)->first(); + + if ($setting) { + $setting->value = $value; + $setting->save(); + } else { + $setting = new Setting(); + $setting->value = $value; + $setting->save(); + } } } diff --git a/app/Services/NodeHelper.php b/app/Services/NodeHelper.php index 276ccfef..361a881b 100644 --- a/app/Services/NodeHelper.php +++ b/app/Services/NodeHelper.php @@ -693,7 +693,7 @@ public function getValidatorStanding() FROM mbs WHERE era_id = $previous_era_id "); - $previous_mbs = (float)($previous_mbs[0]['mbs'] ?? 0); + $previous_mbs = (float)($previous_mbs[0]->mbs ?? 0); foreach ($bids as $b) { $public_key = strtolower($b['public_key'] ?? 'nill'); From 302fdde54aae0d9aa5140ba9613aa5da0e489fa8 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 5 Oct 2022 15:46:00 -0400 Subject: [PATCH 004/162] admin settings --- app/Services/NodeHelper.php | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/app/Services/NodeHelper.php b/app/Services/NodeHelper.php index 361a881b..70d46928 100644 --- a/app/Services/NodeHelper.php +++ b/app/Services/NodeHelper.php @@ -646,42 +646,42 @@ public function getValidatorStanding() } // Get settings for stable eras requirement - $eras_to_be_stable = DB::select(" + $voting_eras_to_vote = DB::select(" SELECT value FROM settings - WHERE name = 'eras_to_be_stable' + WHERE name = 'voting_eras_to_vote' "); - $a = (array)($eras_to_be_stable[0] ?? array()); - $eras_to_be_stable = (int)($a['value'] ?? 0); + $a = (array)($voting_eras_to_vote[0] ?? array()); + $voting_eras_to_vote = (int)($a['value'] ?? 0); // Create setting if not exist - if (!$eras_to_be_stable) { - $eras_to_be_stable = 100; + if (!$voting_eras_to_vote) { + $voting_eras_to_vote = 100; DB::table('settings')->insert( array( - 'name' => 'eras_to_be_stable', + 'name' => 'voting_eras_to_vote', 'value' => '100' ) ); } // Discover black marks - validator has sufficient stake, but failed to win slot. - $eras_look_back = DB::select(" + $uptime_calc_size = DB::select(" SELECT value FROM settings - WHERE name = 'eras_look_back' + WHERE name = 'uptime_calc_size' "); - $a = (array)($eras_look_back[0] ?? array()); - $eras_look_back = (int)($a['value'] ?? 0); + $a = (array)($uptime_calc_size[0] ?? array()); + $uptime_calc_size = (int)($a['value'] ?? 0); // Create setting if not exist - if (!$eras_look_back) { - $eras_look_back = 360; + if (!$uptime_calc_size) { + $uptime_calc_size = 360; DB::table('settings')->insert( array( - 'name' => 'eras_look_back', + 'name' => 'uptime_calc_size', 'value' => '360' ) ); @@ -718,14 +718,14 @@ public function getValidatorStanding() $a = (array)($next_era_weight[0] ?? array()); $next_era_weight = (int)($a['next_era_weight'] ?? 0); - // Calculate historical_performance from past $eras_look_back eras + // Calculate historical_performance from past $uptime_calc_size eras $missed = 0; $in_curent_eras = DB::select(" SELECT in_curent_era FROM all_node_data WHERE public_key = '$public_key' ORDER BY era_id DESC - LIMIT $eras_look_back + LIMIT $uptime_calc_size "); $in_curent_eras = $in_curent_eras ? $in_curent_eras : array(); @@ -753,7 +753,7 @@ public function getValidatorStanding() } $historical_performance = ($uptime * ($window - $missed)) / $window; - $past_era = $current_era_id - $eras_to_be_stable; + $past_era = $current_era_id - $voting_eras_to_vote; $bad_mark = 0; if ($past_era < 0) { @@ -791,7 +791,7 @@ public function getValidatorStanding() $stable = (int)(!$unstable); $stability_count = $stability_count ? count($stability_count) : 0; - if ($stability_count < $eras_to_be_stable) { + if ($stability_count < $voting_eras_to_vote) { $stable = 0; } From 41bd0751d9fe8fb1cdea73107eea08e9a16c6c63 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 6 Oct 2022 10:19:36 -0400 Subject: [PATCH 005/162] redmarks_revoke_lookback setting --- app/Http/Controllers/Api/V1/AdminController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index c75c3cda..cc4addd9 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -1017,6 +1017,7 @@ public function updateGlobalSettings(Request $request) 'voting_eras_to_vote' => ($request->voting_eras_to_vote ?? null), 'voting_eras_since_redmark' => ($request->voting_eras_since_redmark ?? null), 'redmarks_revoke' => ($request->redmarks_revoke ?? null), + 'redmarks_revoke_lookback' => ($request->redmarks_revoke_lookback ?? null), 'responsiveness_warning' => ($request->responsiveness_warning ?? null), 'responsiveness_probation' => ($request->responsiveness_probation ?? null) ]; From 33f24522ab6e662159ed18999e9c1cfe24835068 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 6 Oct 2022 21:57:52 -0400 Subject: [PATCH 006/162] # Updates --- routes/api.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/routes/api.php b/routes/api.php index dc5c7824..8082cfde 100644 --- a/routes/api.php +++ b/routes/api.php @@ -64,7 +64,6 @@ // New My Eras page endpoint Route::get('/users/get-my-eras', [UserController::class, 'getMyEras']); - Route::post('/users/verify-email', [AuthController::class, 'verifyEmail']); Route::post('/users/resend-verify-email', [AuthController::class, 'resendVerifyEmail']); Route::post('/users/change-email', [UserController::class, 'changeEmail']); @@ -121,7 +120,6 @@ // New Nodes page endpoint Route::get('/users/get-nodes-page', [AdminController::class, 'getNodesPage']); - Route::get('/users', [AdminController::class, 'getUsers']); Route::get('/users/{id}', [AdminController::class, 'getUserDetail'])->where('id', '[0-9]+'); Route::get('/dashboard', [AdminController::class, 'infoDashboard']); From 1703ba888425b6d14fcd532174bd8f110213d0ff Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 7 Oct 2022 19:05:33 -0400 Subject: [PATCH 007/162] endpoint return updates --- app/Console/Commands/HistoricalData.php | 558 +++++++++++++++++- .../Controllers/Api/V1/MetricController.php | 1 + .../Controllers/Api/V1/UserController.php | 4 + 3 files changed, 562 insertions(+), 1 deletion(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index 4f54be6f..b6dd999b 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -4,6 +4,9 @@ use App\Services\NodeHelper; +use App\Models\Setting; +use App\Models\DailyEarning; + use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; @@ -32,6 +35,559 @@ public function __construct() public function handle() { - $nodeHelper = new NodeHelper(); + $nodeHelper = new NodeHelper(); + $port8888_responses = array(); + + /* + casper-client get-auction-info --node-address http://18.219.70.138:7777/rpc -b e549a8703cb3bdf2c8e11a7cc72592c3043fe4490737064c6fd9da30cc0d4f36 | jq .result.auction_state.era_validators | jq .[0].era_id + + 011117189c666f81c5160cd610ee383dc9b2d0361f004934754d39752eedc64957 + */ + + $get_block = 'casper-client get-block '; + $get_auction = 'casper-client get-auction-info '; + $node_arg = '--node-address http://18.219.70.138:7777/rpc '; + + $json = shell_exec($get_block.$node_arg); + $json = json_decode($json); + $current_era = (int)($json->result->block->header->era_id ?? 0); + // $historic_era = 4135; // pre-calculated + // $historic_block = 614408; // pre-calculated + $historic_era = 4874; // bookmark + $historic_block = 775014; // bookmark + + // current seconds per era: 152 + + $i = 0; + + while ($current_era > $historic_era) { + // while ($i < 5000) { + // first see if we have this era's auction info + $node_data = DB::select(" + SELECT era_id + FROM all_node_data + WHERE era_id = $historic_era + "); + $node_data = (int)($node_data[0]->era_id ?? 0); + + if ($node_data) { + // era's auction info exists. do not need to fetch. + info('Already have era '.$historic_era.' data. skipping'); + $historic_era += 1; + } else { + // decrease block height and check for new switch block + info('Checking block '.$historic_block.' for era '.$historic_era); + $command = $get_block.$node_arg.'-b '.$historic_block; + $switch_block = shell_exec($command); + $json = json_decode($switch_block); + $era_id = $json->result->block->header->era_id ?? 0; + $block_hash = $json->result->block->hash ?? ''; + + if ($era_id == $historic_era) { + // start timer + $start_time = (int)time(); + + // get auction info for this new detected era switch + info($era_id.' '.$block_hash); + $historic_era += 1; + $command = $get_auction.$node_arg.'-b '.$block_hash; + $auction_info = shell_exec($command); + + + // very large object. aprx 10MB + $decoded_response = json_decode($auction_info); + $auction_state = $decoded_response->result->auction_state ?? array(); + $bids = $auction_state->bids ?? array(); + + // get era ID + $era_validators = $auction_state->era_validators ?? array(); + $current_era_validators = $era_validators[0] ?? array(); + $next_era_validators = $era_validators[1] ?? array(); + $current_era_id = (int)($current_era_validators->era_id ?? 0); + $next_era_id = (int)($next_era_validators->era_id ?? 0); + + info('Data aquired for era: '.$current_era_id); + + // loop current era + $current_validator_weights = $current_era_validators->validator_weights ?? array(); + info('Saving current era validator weights'); + + foreach ($current_validator_weights as $v) { + $public_key = $v->public_key ?? ''; + $weight = (int)($v->weight / 1000000000 ?? 0); + + $check = DB::select(" + SELECT public_key, era_id + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id = $current_era_id + "); + $check = $check[0] ?? null; + + if (!$check) { + DB::table('all_node_data')->insert( + array( + 'public_key' => $public_key, + 'era_id' => $current_era_id, + 'current_era_weight' => $weight, + 'in_curent_era' => 1, + 'created_at' => Carbon::now('UTC') + ) + ); + } + } + + // loop next era + $next_validator_weights = $next_era_validators->validator_weights ?? array(); + info('Saving next era validator weights'); + + foreach ($next_validator_weights as $v) { + $public_key = $v->public_key ?? ''; + $weight = (int)($v->weight / 1000000000 ?? 0); + + $check = DB::select(" + SELECT public_key, era_id + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id = $current_era_id + "); + $check = $check[0] ?? null; + + if (!$check) { + // info('Saved: '.$public_key); + DB::table('all_node_data')->insert( + array( + 'public_key' => $public_key, + 'era_id' => $current_era_id, + 'next_era_weight' => $weight, + 'in_next_era' => 1 + ) + ); + } else { + // info('Updated: '.$public_key); + DB::table('all_node_data') + ->where('public_key', $public_key) + ->where('era_id', $current_era_id) + ->update( + array( + 'next_era_weight' => $weight, + 'in_next_era' => 1 + ) + ); + } + } + + // set MBS array. minimum bid slot amount + $MBS_arr = array(); + + // get global uptimes from MAKE + $global_uptime = $nodeHelper->retrieveGlobalUptime($current_era_id); + + // loop auction era + info('Looping auction era - Saving uptime, bid, and daily earnings data'); + foreach ($bids as $b) { + $public_key = strtolower($b->public_key ?? 'nill'); + $bid = $b->bid ?? array(); + + // get self + $self_staked_amount = (int)($bid->staked_amount ?? 0); + $delegation_rate = (int)($bid->delegation_rate ?? 0); + $bid_inactive = (int)($bid->inactive ?? false); + + // calculate total stake, delegators + self stake + $delegators = (array)($bid->delegators ?? array()); + $delegators_count = count($delegators); + $delegators_staked_amount = 0; + + foreach ($delegators as $delegator) { + $delegators_staked_amount += (int)($delegator->staked_amount ?? 0); + } + + // convert and calculate stake amounts + $delegators_staked_amount = (int)($delegators_staked_amount / 1000000000); + $self_staked_amount = (int)($self_staked_amount / 1000000000); + $total_staked_amount = $delegators_staked_amount + $self_staked_amount; + + // append to MBS array and pluck 100th place later + $MBS_arr[$public_key] = $total_staked_amount; + + // get node uptime from MAKE object + $uptime = 0; + + foreach ($global_uptime as $uptime_array) { + $fvid = strtolower($uptime_array->public_key ?? ''); + + if($fvid == $public_key) { + $uptime = (float)($uptime_array->average_score ?? 0); + break; + } + } + + // record auction bid data for this node + $check = DB::select(" + SELECT public_key, era_id + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id = $current_era_id + "); + $check = $check[0] ?? null; + + if (!$check) { + // info('Saved: '.$public_key); + DB::table('all_node_data')->insert( + array( + 'public_key' => $public_key, + 'era_id' => $current_era_id, + 'uptime' => $uptime, + 'in_auction' => 1, + 'bid_delegators_count' => $delegators_count, + 'bid_delegation_rate' => $delegation_rate, + 'bid_inactive' => $bid_inactive, + 'bid_self_staked_amount' => $self_staked_amount, + 'bid_delegators_staked_amount' => $delegators_staked_amount, + 'bid_total_staked_amount' => $total_staked_amount + ) + ); + } else { + // info('Updated: '.$public_key); + DB::table('all_node_data') + ->where('public_key', $public_key) + ->where('era_id', $current_era_id) + ->update( + array( + 'uptime' => $uptime, + 'in_auction' => 1, + 'bid_delegators_count' => $delegators_count, + 'bid_delegation_rate' => $delegation_rate, + 'bid_inactive' => $bid_inactive, + 'bid_self_staked_amount' => $self_staked_amount, + 'bid_delegators_staked_amount' => $delegators_staked_amount, + 'bid_total_staked_amount' => $total_staked_amount + ) + ); + } + + // save current stake amount to daily earnings table + $earning = new DailyEarning(); + $earning->node_address = $public_key; + $earning->self_staked_amount = (int)$self_staked_amount; + $earning->created_at = Carbon::now('UTC'); + $earning->save(); + + // get difference between current self stake and yesterdays self stake + $get_earning = DailyEarning::where('node_address', $public_key) + ->where('created_at', '>', Carbon::now('UTC')->subHours(24)) + ->orderBy('created_at', 'asc') + ->first(); + + $yesterdays_self_staked_amount = (int)($get_earning->self_staked_amount ?? 0); + $daily_earning = $self_staked_amount - $yesterdays_self_staked_amount; + + // look for existing peer by public key for port 8888 data + foreach ($port8888_responses as $port8888data) { + $our_public_signing_key = strtolower($port8888data['our_public_signing_key'] ?? ''); + + if ($our_public_signing_key == $public_key) { + // found in peers + $peer_count = (int)($port8888data['peer_count'] ?? 0); + $era_id = (int)($port8888data['last_added_block_info']['era_id'] ?? 0); + $block_height = (int)($port8888data['last_added_block_info']['height'] ?? 0); + $build_version = $port8888data['build_version'] ?? '1.0.0'; + $build_version = explode('-', $build_version)[0]; + $chainspec_name = $port8888data['chainspec_name'] ?? 'casper'; + $next_upgrade = $port8888data['next_upgrade']['protocol_version'] ?? ''; + + // record port 8888 data if available + $check = DB::select(" + SELECT public_key, era_id + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id = $current_era_id + "); + $check = $check[0] ?? null; + + if (!$check) { + DB::table('all_node_data')->insert( + array( + 'public_key' => $public_key, + 'era_id' => $current_era_id, + 'port8888_peers' => $peer_count, + 'port8888_era_id' => $era_id, + 'port8888_block_height' => $block_height, + 'port8888_build_version' => $build_version, + 'port8888_next_upgrade' => $next_upgrade + ) + ); + } else { + DB::table('all_node_data') + ->where('public_key', $public_key) + ->where('era_id', $current_era_id) + ->update( + array( + 'port8888_peers' => $peer_count, + 'port8888_era_id' => $era_id, + 'port8888_block_height' => $block_height, + 'port8888_build_version' => $build_version, + 'port8888_next_upgrade' => $next_upgrade + ) + ); + } + + break; + } + } + } + + // find MBS + info('Finding MBS for this era'); + rsort($MBS_arr); + $MBS = 0; + + if (count($MBS_arr) > 0) { + $MBS = (float)($MBS_arr[99] ?? $MBS_arr[count($MBS_arr) - 1]); + } + + // save MBS in new table by current_era + $check = DB::select(" + SELECT mbs + FROM mbs + WHERE era_id = $current_era_id + "); + $check = $check[0] ?? null; + + if (!$check) { + DB::table('mbs')->insert( + array( + 'era_id' => $current_era_id, + 'mbs' => $MBS, + 'created_at' => Carbon::now('UTC') + ) + ); + } else { + DB::table('mbs') + ->where('era_id', $current_era_id) + ->update( + array( + 'mbs' => $MBS, + 'updated_at' => Carbon::now('UTC') + ) + ); + } + + // Get settings for stable eras requirement + info('Getting settings'); + $voting_eras_to_vote = DB::select(" + SELECT value + FROM settings + WHERE name = 'voting_eras_to_vote' + "); + $a = (array)($voting_eras_to_vote[0] ?? array()); + $voting_eras_to_vote = (int)($a['value'] ?? 0); + + // Create setting if not exist + if (!$voting_eras_to_vote) { + $voting_eras_to_vote = 100; + + DB::table('settings')->insert( + array( + 'name' => 'voting_eras_to_vote', + 'value' => '100' + ) + ); + } + + // Discover black marks - validator has sufficient stake, but failed to win slot. + $uptime_calc_size = DB::select(" + SELECT value + FROM settings + WHERE name = 'uptime_calc_size' + "); + $a = (array)($uptime_calc_size[0] ?? array()); + $uptime_calc_size = (int)($a['value'] ?? 0); + + // Create setting if not exist + if (!$uptime_calc_size) { + $uptime_calc_size = 360; + + DB::table('settings')->insert( + array( + 'name' => 'uptime_calc_size', + 'value' => '360' + ) + ); + } + + $previous_era_id = $current_era_id - 1; + $previous_mbs = DB::select(" + SELECT mbs + FROM mbs + WHERE era_id = $previous_era_id + "); + $previous_mbs = (float)($previous_mbs[0]->mbs ?? 0); + + info('Looping auction era object again to find bad marks and historical performance'); + foreach ($bids as $b) { + $public_key = strtolower($b->public_key ?? 'nill'); + info('Complete: '.$public_key); + + /* + // last era + $previous_stake = DB::select(" + SELECT bid_total_staked_amount + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id = $previous_era_id + "); + $a = (array)($previous_stake[0] ?? array()); + $previous_stake = (int)($a['bid_total_staked_amount'] ?? 0); + + // current era + $next_era_weight = DB::select(" + SELECT next_era_weight + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id = $current_era_id + "); + $a = (array)($next_era_weight[0] ?? array()); + $next_era_weight = (int)($a['next_era_weight'] ?? 0); + */ + + + // last era and current era + $multi_query = DB::select(" + SELECT + era_id, bid_total_staked_amount, next_era_weight + FROM all_node_data + WHERE public_key = '$public_key' + AND ( + era_id = $previous_era_id OR + era_id = $current_era_id + ) + "); + + $previous_stake = 0; + $next_era_weight = 0; + + if ($multi_query) { + foreach ($multi_query as $result) { + if ($result->era_id == $previous_era_id) { + $previous_stake = (int)($result->bid_total_staked_amount); + } + + if ($result->era_id == $current_era_id) { + $next_era_weight = (int)($result->next_era_weight); + } + } + } + + + // Calculate historical_performance from past $uptime_calc_size eras + $missed = 0; + $in_curent_eras = DB::select(" + SELECT in_curent_era + FROM all_node_data + WHERE public_key = '$public_key' + ORDER BY era_id DESC + LIMIT $uptime_calc_size + "); + + $in_curent_eras = $in_curent_eras ? $in_curent_eras : array(); + $window = $in_curent_eras ? count($in_curent_eras) : 0; + + foreach ($in_curent_eras as $c) { + $in = (bool)($c->in_curent_era ?? 0); + + if (!$in) { + $missed += 1; + } + } + + // get node uptime from MAKE object + $uptime = 0; + + foreach ($global_uptime as $uptime_array) { + $fvid = strtolower($uptime_array->public_key ?? ''); + + if($fvid == $public_key) { + $uptime = (float)($uptime_array->average_score ?? 0); + break; + } + } + + $historical_performance = ($uptime * ($window - $missed)) / $window; + $past_era = $current_era_id - $voting_eras_to_vote; + $bad_mark = 0; + + if ($past_era < 0) { + $past_era = 0; + } + + if ( + $previous_stake > $previous_mbs && + !$next_era_weight + ) { + // black mark + $bad_mark = 1; + } + + // Update stability + $unstable = DB::select(" + SELECT era_id + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id > $past_era + AND ( + bad_mark = 1 OR + bid_inactive = 1 + ) + "); + + $stability_count = DB::select(" + SELECT era_id + FROM all_node_data + WHERE public_key = '$public_key' + AND era_id > $past_era + "); + + $unstable = (bool)($unstable); + $stable = (int)(!$unstable); + $stability_count = $stability_count ? count($stability_count) : 0; + + if ($stability_count < $voting_eras_to_vote) { + $stable = 0; + } + + DB::table('all_node_data') + ->where('public_key', $public_key) + ->where('era_id', $current_era_id) + ->update( + array( + 'bad_mark' => $bad_mark, + 'stable' => $stable, + 'historical_performance' => $historical_performance + ) + ); + } + + // DailyEarning garbage cleanup + DailyEarning::where( + 'created_at', + '<', + Carbon::now('UTC')->subDays(90) + )->delete(); + + // end timer + $end_time = (int)time(); + + info("Time spent on era: ".($end_time - $start_time)); + } + + $historic_block += 1; + } + + $i += 1; + } + + info('done'); } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/V1/MetricController.php b/app/Http/Controllers/Api/V1/MetricController.php index b9b35804..ee1cae53 100644 --- a/app/Http/Controllers/Api/V1/MetricController.php +++ b/app/Http/Controllers/Api/V1/MetricController.php @@ -542,6 +542,7 @@ public function getMetricUserByNodeName($node) "); $return_object['monitoring_criteria'] = $monitoring_criteria; info($return_object); + return $this->successResponse($return_object); //// done diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index c1622f5c..a1741aae 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -910,6 +910,7 @@ public function getVerifiedMembers(Request $request) { AND a.era_id = $current_era_id "); info($members); + return $this->successResponse($data); //// done @@ -2113,6 +2114,7 @@ public function getMembers(Request $request) } info($members); + return $this->successResponse($members); //// done @@ -2325,6 +2327,7 @@ public function getMemberDetail($id, Request $request) ) OR b.id = $id "); info($response); + return $this->successResponse($response); //// done @@ -2739,6 +2742,7 @@ public function getListNodes(Request $request) AND a.id = $user_id "); info($nodes); + return $this->successResponse($nodes); From c41adfc9dbd3f34735bec3512e81aedbc238c323 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Sun, 9 Oct 2022 09:46:14 -0400 Subject: [PATCH 008/162] # Updates --- routes/api.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/api.php b/routes/api.php index 8082cfde..8dcfa219 100644 --- a/routes/api.php +++ b/routes/api.php @@ -60,7 +60,7 @@ // New Nodes page endpoint Route::get('/users/get-nodes-page', [UserController::class, 'getNodesPage']); - + // New My Eras page endpoint Route::get('/users/get-my-eras', [UserController::class, 'getMyEras']); From f7c9637ddcf508cdf813ca0197168e2cb70ace38 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Mon, 10 Oct 2022 09:09:12 -0400 Subject: [PATCH 009/162] # Updates --- app/Http/Controllers/Api/V1/UserController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 8500052d..18b112a3 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -910,7 +910,7 @@ public function getVerifiedMembers(Request $request) { AND a.era_id = $current_era_id "); info($members); - return $this->successResponse($data); + return $this->successResponse($members); //// done From a3bc2ef35f259590cb381840418e65ec82402bf5 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 10 Oct 2022 10:04:25 -0400 Subject: [PATCH 010/162] update --- app/Console/Commands/HistoricalData.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index b6dd999b..81304dc9 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -53,10 +53,10 @@ public function handle() $current_era = (int)($json->result->block->header->era_id ?? 0); // $historic_era = 4135; // pre-calculated // $historic_block = 614408; // pre-calculated - $historic_era = 4874; // bookmark - $historic_block = 775014; // bookmark + $historic_era = 5701; // bookmark + $historic_block = 955831; // bookmark - // current seconds per era: 152 + // current seconds per era: 296 $i = 0; From 34b3a2e493356e77fb58ed7cb15d826e0811d28c Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 10 Oct 2022 18:21:20 -0400 Subject: [PATCH 011/162] new node data table --- app/Console/Commands/HistoricalData.php | 363 +++++++++++- .../Controllers/Api/V1/UserController.php | 516 ++++++++++++------ ...022_10_10_145105_create_all_node_data2.php | 62 +++ 3 files changed, 745 insertions(+), 196 deletions(-) create mode 100644 database/migrations/2022_10_10_145105_create_all_node_data2.php diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index b6dd999b..28a2be75 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -42,8 +42,336 @@ public function handle() casper-client get-auction-info --node-address http://18.219.70.138:7777/rpc -b e549a8703cb3bdf2c8e11a7cc72592c3043fe4490737064c6fd9da30cc0d4f36 | jq .result.auction_state.era_validators | jq .[0].era_id 011117189c666f81c5160cd610ee383dc9b2d0361f004934754d39752eedc64957 + + 34 seconds per era + 47 seconds per era */ + $get_block = 'casper-client get-block '; + $get_auction = 'casper-client get-auction-info '; + $node_arg = '--node-address http://18.219.70.138:7777/rpc '; + + $json = shell_exec($get_block.$node_arg); + $json = json_decode($json); + $current_era = (int)($json->result->block->header->era_id ?? 0); + // $historic_era = 4135; // pre-calculated + // $historic_block = 614408; // pre-calculated + $historic_era = 4367; // bookmark + $historic_block = 664698; // bookmark + + while ($current_era > $historic_era) { + // first see if we have this era's auction info + $node_data = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE era_id = $historic_era + "); + $node_data = (int)($node_data[0]->era_id ?? 0); + + if ($node_data) { + // era's auction info exists. do not need to fetch. + info('Already have era '.$historic_era.' data. skipping'); + $historic_era += 1; + } else { + // decrease block height and check for new switch block + info('Checking block '.$historic_block.' for era '.$historic_era); + $command = $get_block.$node_arg.'-b '.$historic_block; + $switch_block = shell_exec($command); + $json = json_decode($switch_block); + $era_id = $json->result->block->header->era_id ?? 0; + $block_hash = $json->result->block->hash ?? ''; + + if ($era_id == $historic_era) { + // start timer + $start_time = (int)time(); + + // get auction info for this new detected era switch + info($era_id.' '.$block_hash); + $historic_era += 1; + $command = $get_auction.$node_arg.'-b '.$block_hash; + $auction_info = shell_exec($command); + + + // very large object. aprx 10MB + $decoded_response = json_decode($auction_info); + $auction_state = $decoded_response->result->auction_state ?? array(); + $bids = $auction_state->bids ?? array(); + + // get era ID + $era_validators = $auction_state->era_validators ?? array(); + $current_era_validators = $era_validators[0] ?? array(); + $next_era_validators = $era_validators[1] ?? array(); + $current_era_id = (int)($current_era_validators->era_id ?? 0); + $next_era_id = (int)($next_era_validators->era_id ?? 0); + + info('Data aquired for era: '.$current_era_id); + + // set MBS array. minimum bid slot amount + $MBS_arr = array(); + + // get global uptimes from MAKE + $global_uptime = $nodeHelper->retrieveGlobalUptime($current_era_id); + + // loop auction era + info('Looping auction era - Saving uptime, bid, and daily earnings data'); + foreach ($bids as $b) { + $public_key = strtolower($b->public_key ?? 'nill'); + $bid = $b->bid ?? array(); + + // get self + $self_staked_amount = (int)($bid->staked_amount ?? 0); + $delegation_rate = (int)($bid->delegation_rate ?? 0); + $bid_inactive = (int)($bid->inactive ?? false); + + // calculate total stake, delegators + self stake + $delegators = (array)($bid->delegators ?? array()); + $delegators_count = count($delegators); + $delegators_staked_amount = 0; + + foreach ($delegators as $delegator) { + $delegators_staked_amount += (int)($delegator->staked_amount ?? 0); + } + + // convert and calculate stake amounts + $delegators_staked_amount = (int)($delegators_staked_amount / 1000000000); + $self_staked_amount = (int)($self_staked_amount / 1000000000); + $total_staked_amount = $delegators_staked_amount + $self_staked_amount; + + // append to MBS array and pluck 100th place later + $MBS_arr[$public_key] = $total_staked_amount; + + // get node uptime from MAKE object + $uptime = 0; + + foreach ($global_uptime as $uptime_array) { + $fvid = strtolower($uptime_array->public_key ?? ''); + + if($fvid == $public_key) { + $uptime = (float)($uptime_array->average_score ?? 0); + break; + } + } + + DB::table('all_node_data2')->insert( + array( + 'public_key' => $public_key, + 'era_id' => $current_era_id, + 'created_at' => Carbon::now('UTC'), + 'uptime' => $uptime, + 'in_auction' => 1, + 'bid_delegators_count' => $delegators_count, + 'bid_delegation_rate' => $delegation_rate, + 'bid_inactive' => $bid_inactive, + 'bid_self_staked_amount' => $self_staked_amount, + 'bid_delegators_staked_amount' => $delegators_staked_amount, + 'bid_total_staked_amount' => $total_staked_amount + ) + ); + + // save current stake amount to daily earnings table + $earning = new DailyEarning(); + $earning->node_address = $public_key; + $earning->self_staked_amount = (int)$self_staked_amount; + $earning->created_at = Carbon::now('UTC'); + $earning->save(); + + // get difference between current self stake and yesterdays self stake + $get_earning = DailyEarning::where('node_address', $public_key) + ->where('created_at', '>', Carbon::now('UTC')->subHours(24)) + ->orderBy('created_at', 'asc') + ->first(); + + $yesterdays_self_staked_amount = (int)($get_earning->self_staked_amount ?? 0); + $daily_earning = $self_staked_amount - $yesterdays_self_staked_amount; + + // look for existing peer by public key for port 8888 data + foreach ($port8888_responses as $port8888data) { + $our_public_signing_key = strtolower($port8888data['our_public_signing_key'] ?? ''); + + if ($our_public_signing_key == $public_key) { + // found in peers + $peer_count = (int)($port8888data['peer_count'] ?? 0); + $era_id = (int)($port8888data['last_added_block_info']['era_id'] ?? 0); + $block_height = (int)($port8888data['last_added_block_info']['height'] ?? 0); + $build_version = $port8888data['build_version'] ?? '1.0.0'; + $build_version = explode('-', $build_version)[0]; + $chainspec_name = $port8888data['chainspec_name'] ?? 'casper'; + $next_upgrade = $port8888data['next_upgrade']['protocol_version'] ?? ''; + + DB::table('all_node_data2') + ->where('public_key', $public_key) + ->where('era_id', $current_era_id) + ->update( + array( + 'port8888_peers' => $peer_count, + 'port8888_era_id' => $era_id, + 'port8888_block_height' => $block_height, + 'port8888_build_version' => $build_version, + 'port8888_next_upgrade' => $next_upgrade + ) + ); + + break; + } + } + } + + // loop current era + $current_validator_weights = $current_era_validators->validator_weights ?? array(); + info('Saving current era validator weights'); + + foreach ($current_validator_weights as $v) { + $public_key = $v->public_key ?? ''; + $weight = (int)($v->weight / 1000000000 ?? 0); + + $check = DB::select(" + SELECT public_key, era_id + FROM all_node_data2 + WHERE public_key = '$public_key' + AND era_id = $current_era_id + "); + $check = $check[0] ?? null; + + if (!$check) { + DB::table('all_node_data2')->insert( + array( + 'public_key' => $public_key, + 'era_id' => $current_era_id, + 'current_era_weight' => $weight, + 'in_current_era' => 1, + 'created_at' => Carbon::now('UTC') + ) + ); + } else { + DB::table('all_node_data2') + ->where('public_key', $public_key) + ->where('era_id', $current_era_id) + ->update( + array( + 'current_era_weight' => $weight, + 'in_current_era' => 1 + ) + ); + } + } + + // loop next era + $next_validator_weights = $next_era_validators->validator_weights ?? array(); + info('Saving next era validator weights'); + + foreach ($next_validator_weights as $v) { + $public_key = $v->public_key ?? ''; + $weight = (int)($v->weight / 1000000000 ?? 0); + + $check = DB::select(" + SELECT public_key, era_id + FROM all_node_data2 + WHERE public_key = '$public_key' + AND era_id = $current_era_id + "); + $check = $check[0] ?? null; + + if (!$check) { + // info('Saved: '.$public_key); + DB::table('all_node_data2')->insert( + array( + 'public_key' => $public_key, + 'era_id' => $current_era_id, + 'next_era_weight' => $weight, + 'in_next_era' => 1, + 'created_at' => Carbon::now('UTC') + ) + ); + } else { + // info('Updated: '.$public_key); + DB::table('all_node_data2') + ->where('public_key', $public_key) + ->where('era_id', $current_era_id) + ->update( + array( + 'next_era_weight' => $weight, + 'in_next_era' => 1 + ) + ); + } + } + + // find MBS + info('Finding MBS for this era'); + rsort($MBS_arr); + $MBS = 0; + + if (count($MBS_arr) > 0) { + $MBS = (float)($MBS_arr[99] ?? $MBS_arr[count($MBS_arr) - 1]); + } + + // save MBS in new table by current_era + $check = DB::select(" + SELECT mbs + FROM mbs + WHERE era_id = $current_era_id + "); + $check = $check[0] ?? null; + + if (!$check) { + DB::table('mbs')->insert( + array( + 'era_id' => $current_era_id, + 'mbs' => $MBS, + 'created_at' => Carbon::now('UTC') + ) + ); + } else { + DB::table('mbs') + ->where('era_id', $current_era_id) + ->update( + array( + 'mbs' => $MBS, + 'updated_at' => Carbon::now('UTC') + ) + ); + } + + // DailyEarning garbage cleanup + DailyEarning::where( + 'created_at', + '<', + Carbon::now('UTC')->subDays(90) + )->delete(); + + // end timer + $end_time = (int)time(); + + info("Time spent on era: ".($end_time - $start_time)); + } + + $historic_block += 1; + } + } + + + + + + + + + + + + + + + + + + + + /* + OLD + + + $get_block = 'casper-client get-block '; $get_auction = 'casper-client get-auction-info '; $node_arg = '--node-address http://18.219.70.138:7777/rpc '; @@ -430,27 +758,25 @@ public function handle() $public_key = strtolower($b->public_key ?? 'nill'); info('Complete: '.$public_key); - /* // last era - $previous_stake = DB::select(" - SELECT bid_total_staked_amount - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id = $previous_era_id - "); - $a = (array)($previous_stake[0] ?? array()); - $previous_stake = (int)($a['bid_total_staked_amount'] ?? 0); + // $previous_stake = DB::select(" + // SELECT bid_total_staked_amount + // FROM all_node_data + // WHERE public_key = '$public_key' + // AND era_id = $previous_era_id + // "); + // $a = (array)($previous_stake[0] ?? array()); + // $previous_stake = (int)($a['bid_total_staked_amount'] ?? 0); // current era - $next_era_weight = DB::select(" - SELECT next_era_weight - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id = $current_era_id - "); - $a = (array)($next_era_weight[0] ?? array()); - $next_era_weight = (int)($a['next_era_weight'] ?? 0); - */ + // $next_era_weight = DB::select(" + // SELECT next_era_weight + // FROM all_node_data + // WHERE public_key = '$public_key' + // AND era_id = $current_era_id + // "); + // $a = (array)($next_era_weight[0] ?? array()); + // $next_era_weight = (int)($a['next_era_weight'] ?? 0); // last era and current era @@ -588,6 +914,7 @@ public function handle() $i += 1; } + */ info('done'); } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 18b112a3..82f285b2 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -116,7 +116,7 @@ public function getUserDashboard() { // Get current era $current_era_id = DB::select(" SELECT era_id - FROM all_node_data + FROM all_node_data2 ORDER BY era_id DESC LIMIT 1 "); @@ -147,7 +147,7 @@ public function getUserDashboard() { a.public_key, c.id, c.pseudonym, c.node_status, d.status, d.extra_status - FROM all_node_data AS a + FROM all_node_data2 AS a JOIN user_addresses AS b ON a.public_key = b.public_address_node JOIN users AS c @@ -190,17 +190,15 @@ public function getUserDashboard() { // find rank $ranking = DB::select(" SELECT - public_key, - historical_performance AS uptime, + public_key, uptime, bid_delegators_count, bid_delegation_rate, bid_total_staked_amount - FROM all_node_data - WHERE era_id = $current_era_id - AND in_curent_era = 1 - AND in_next_era = 1 - AND in_auction = 1 - AND bad_mark = 0 + FROM all_node_data2 + WHERE era_id = $current_era_id + AND in_current_era = 1 + AND in_next_era = 1 + AND in_auction = 1 "); $max_delegators = 0; $max_stake_amount = 0; @@ -266,18 +264,18 @@ function($x, $y) { $i += 1; } - $return["ranking"] = $sorted_ranking; + $return["ranking"] = $sorted_ranking; + $return["node_rank_total"] = count($sorted_ranking); // parse node addresses $addresses = DB::select(" SELECT a.public_key, - a.historical_performance AS uptime, + a.uptime, a.bid_delegators_count AS delegators, a.port8888_peers AS peers, - a.bid_self_staked_amount, a.bid_total_staked_amount, - a.bad_mark, a.stable - FROM all_node_data AS a + a.bid_self_staked_amount, a.bid_total_staked_amount + FROM all_node_data2 AS a JOIN user_addresses AS b ON a.public_key = b.public_address_node JOIN users AS c @@ -290,38 +288,56 @@ function($x, $y) { $addresses = array(); } + // get settings + $voting_eras_to_vote = DB::select(" + SELECT value + FROM settings + WHERE name = 'voting_eras_to_vote' + "); + $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); + $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); + + $uptime_calc_size = DB::select(" + SELECT value + FROM settings + WHERE name = 'uptime_calc_size' + "); + $uptime_calc_size = $uptime_calc_size[0] ?? array(); + $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); + $past_era = $current_era_id - $uptime_calc_size; + + if ($past_era < 1) { + $past_era = 1; + } + // for each address belonging to a user foreach ($addresses as $address) { - $a = $address->public_key ?? ''; - - $eras_sinse_bad_mark = DB::select(" - SELECT a.era_id - FROM all_node_data AS a - JOIN user_addresses AS b - ON a.public_key = b.public_address_node - WHERE a.public_key = '$a' - AND a.bad_mark = 1 + $p = $address->public_key ?? ''; + + $total_bad_marks = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$p' + AND era_id > $past_era + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) ORDER BY era_id DESC - LIMIT 1 "); - $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; - $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; + + $eras_sinse_bad_mark = $total_bad_marks[0] ?? array(); + $eras_sinse_bad_mark = $current_era_id - (int)($eras_sinse_bad_mark->era_id ?? 0); + $total_bad_marks = count((array)$total_bad_marks); if ($eras_sinse_bad_mark < $return["eras_sinse_bad_mark"]) { $return["eras_sinse_bad_mark"] = $eras_sinse_bad_mark; } - $total_bad_marks = DB::select(" - SELECT bad_mark - FROM all_node_data - WHERE bad_mark = 1 - AND public_key = '$a' - "); - $eras_active = DB::select(" SELECT era_id - FROM all_node_data - WHERE public_key = '$a' + FROM all_node_data2 + WHERE public_key = '$p' ORDER BY era_id ASC LIMIT 1 "); @@ -332,21 +348,45 @@ function($x, $y) { $return["eras_active"] = $current_era_id - $eras_active; } + // Calculate historical_performance from past $uptime_calc_size eras + $missed = 0; + $in_current_eras = DB::select(" + SELECT in_current_era + FROM all_node_data2 + WHERE public_key = '$p' + ORDER BY era_id DESC + LIMIT $uptime_calc_size + "); + + $in_current_eras = $in_current_eras ? $in_current_eras : array(); + $window = $in_current_eras ? count($in_current_eras) : 0; + + foreach ($in_current_eras as $c) { + $in = (bool)($c->in_current_era ?? 0); + + if (!$in) { + $missed += 1; + } + } + + $uptime = (float)($address->uptime ?? 0); + $historical_performance = ($uptime * ($window - $missed)) / $window; + if ( - array_key_exists($a, $return["ranking"]) && ( + array_key_exists($p, $return["ranking"]) && ( $return["node_rank"] == 0 || - $return["ranking"][$a] < $return["node_rank"] + $return["ranking"][$p] < $return["node_rank"] ) ) { - $return["node_rank"] = $return["ranking"][$a]; + $return["node_rank"] = $return["ranking"][$p]; } - $return["total_bad_marks"] += (int)(count($total_bad_marks ?? array())); + $return["total_bad_marks"] += $total_bad_marks; $return["total_stake"] += (int)($address->bid_total_staked_amount ?? 0); $return["total_self_stake"] += (int)($address->bid_self_staked_amount ?? 0); $return["total_delegators"] += (int)($address->delegators ?? 0); $return["peers"] += (int)($address->peers ?? 0); - $return["uptime"] += (float)($address->uptime ?? 0); + $return["uptime"] += $historical_performance; } $addresses_count = count($addresses); @@ -367,7 +407,7 @@ public function getMembershipPage() { // Get current era $current_era_id = DB::select(" SELECT era_id - FROM all_node_data + FROM all_node_data2 ORDER BY era_id DESC LIMIT 1 "); @@ -388,12 +428,11 @@ public function getMembershipPage() { $addresses = DB::select(" SELECT - a.public_key, - a.historical_performance AS uptime, + a.public_key, a.uptime, a.port8888_peers AS peers, - a.bid_inactive, a.bad_mark, a.stable, + a.bid_inactive, a.in_current_era, c.status AS kyc_status - FROM all_node_data AS a + FROM all_node_data2 AS a JOIN user_addresses AS b ON a.public_key = b.public_address_node LEFT JOIN shuftipro AS c @@ -413,46 +452,83 @@ public function getMembershipPage() { $return["kyc_status"] = "Verified"; } + $uptime_calc_size = DB::select(" + SELECT value + FROM settings + WHERE name = 'uptime_calc_size' + "); + $uptime_calc_size = $uptime_calc_size[0] ?? array(); + $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); + $past_era = $current_era_id - $uptime_calc_size; + + if ($past_era < 1) { + $past_era = 1; + } + foreach ($addresses as $address) { - $a = $address->public_key ?? ''; - - $eras_sinse_bad_mark = DB::select(" - SELECT a.era_id - FROM all_node_data AS a - JOIN user_addresses AS b - ON a.public_key = b.public_address_node - WHERE a.public_key = '$a' - AND a.bad_mark = 1 + $p = $address->public_key ?? ''; + + $total_bad_marks = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$p' + AND era_id > $past_era + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) ORDER BY era_id DESC - LIMIT 1 "); - $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; - $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; + + $eras_sinse_bad_mark = $total_bad_marks[0] ?? array(); + $eras_sinse_bad_mark = $current_era_id - (int)($eras_sinse_bad_mark->era_id ?? 0); + $total_bad_marks = count((array)$total_bad_marks); if ($eras_sinse_bad_mark < $return["eras_sinse_bad_mark"]) { $return["eras_sinse_bad_mark"] = $eras_sinse_bad_mark; } - $total_bad_marks = DB::select(" - SELECT bad_mark - FROM all_node_data - WHERE bad_mark = 1 - AND public_key = '$a' - "); - $total_eras = DB::select(" SELECT era_id - FROM all_node_data - WHERE public_key = '$a' + FROM all_node_data2 + WHERE public_key = '$p' ORDER BY era_id ASC LIMIT 1 "); - $total_eras = (int)($total_eras[0]->era_id ?? 0); - $return["total_eras"] = $current_era_id - $total_eras; - $return["total_bad_marks"] += (int)(count($total_bad_marks ?? array())); + $total_eras = (int)($total_eras[0]->era_id ?? 0); + + if ($current_era_id - $total_eras > $return["total_eras"]) { + $return["total_eras"] = $current_era_id - $total_eras; + } + + // Calculate historical_performance from past $uptime_calc_size eras + $missed = 0; + $in_current_eras = DB::select(" + SELECT in_current_era + FROM all_node_data2 + WHERE public_key = '$p' + ORDER BY era_id DESC + LIMIT $uptime_calc_size + "); + + $in_current_eras = $in_current_eras ? $in_current_eras : array(); + $window = $in_current_eras ? count($in_current_eras) : 0; + + foreach ($in_current_eras as $c) { + $in = (bool)($c->in_current_era ?? 0); + + if (!$in) { + $missed += 1; + } + } + + $uptime = (float)($address->uptime ?? 0); + $historical_performance = ($uptime * ($window - $missed)) / $window; + + $return["total_bad_marks"] += $total_bad_marks; $return["peers"] += (int)($address->peers ?? 0); - $return["uptime"][$a] = (float)($address->uptime ?? 0); + $return["uptime"][$p] = (float)($address->uptime ?? 0); $return["avg_uptime"] += (float)($address->uptime ?? 0); if ((int)$address->bid_inactive == 0) { @@ -475,7 +551,7 @@ public function getNodesPage() { // Get current era $current_era_id = DB::select(" SELECT era_id - FROM all_node_data + FROM all_node_data2 ORDER BY era_id DESC LIMIT 1 "); @@ -515,16 +591,15 @@ public function getNodesPage() { // get ranking $ranking = DB::select(" SELECT - public_key, - historical_performance AS uptime, + public_key, uptime, bid_delegators_count, bid_delegation_rate, bid_total_staked_amount - FROM all_node_data - WHERE in_curent_era = 1 - AND in_next_era = 1 - AND in_auction = 1 - AND bad_mark = 0 + FROM all_node_data2 + WHERE era_id = $current_era_id + AND in_current_era = 1 + AND in_next_era = 1 + AND in_auction = 1 "); $max_delegators = 0; $max_stake_amount = 0; @@ -596,10 +671,9 @@ function($x, $y) { SELECT a.public_key, a.bid_delegators_count, a.bid_total_staked_amount, a.bid_self_staked_amount, - a.historical_performance AS uptime, - a.port8888_peers AS peers, - a.bid_inactive, a.bad_mark, a.stable - FROM all_node_data AS a + a.uptime, a.bid_inactive, a.in_current_era, + a.port8888_peers AS peers + FROM all_node_data2 AS a JOIN user_addresses AS b ON a.public_key = b.public_address_node JOIN users AS c @@ -612,47 +686,79 @@ function($x, $y) { $addresses = array(); } + $uptime_calc_size = DB::select(" + SELECT value + FROM settings + WHERE name = 'uptime_calc_size' + "); + $uptime_calc_size = $uptime_calc_size[0] ?? array(); + $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); + $past_era = $current_era_id - $uptime_calc_size; + + if ($past_era < 1) { + $past_era = 1; + } + // for each member's node address foreach ($addresses as $address) { - $a = $address->public_key ?? ''; - - $eras_sinse_bad_mark = DB::select(" - SELECT a.era_id - FROM all_node_data AS a - JOIN user_addresses AS b - ON a.public_key = b.public_address_node - WHERE a.public_key = '$a' - AND a.bad_mark = 1 - ORDER BY era_id DESC - LIMIT 1 - "); - $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; - $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; + $p = $address->public_key ?? ''; $total_bad_marks = DB::select(" - SELECT bad_mark - FROM all_node_data - WHERE bad_mark = 1 - AND public_key = '$a' + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$p' + AND era_id > $past_era + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) + ORDER BY era_id DESC "); + $eras_sinse_bad_mark = $total_bad_marks[0] ?? array(); + $eras_sinse_bad_mark = $current_era_id - (int)($eras_sinse_bad_mark->era_id ?? 0); + $total_bad_marks = count((array)$total_bad_marks); + $total_eras = DB::select(" SELECT era_id - FROM all_node_data - WHERE public_key = '$a' + FROM all_node_data2 + WHERE public_key = '$p' ORDER BY era_id ASC LIMIT 1 "); $total_eras = (int)($total_eras[0]->era_id ?? 0); - $total_eras = $current_era_id - $total_eras; + + // Calculate historical_performance from past $uptime_calc_size eras + $missed = 0; + $in_current_eras = DB::select(" + SELECT in_current_era + FROM all_node_data2 + WHERE public_key = '$p' + ORDER BY era_id DESC + LIMIT $uptime_calc_size + "); + + $in_current_eras = $in_current_eras ? $in_current_eras : array(); + $window = $in_current_eras ? count($in_current_eras) : 0; + + foreach ($in_current_eras as $c) { + $in = (bool)($c->in_current_era ?? 0); + + if (!$in) { + $missed += 1; + } + } + + $uptime = (float)($address->uptime ?? 0); + $historical_performance = ($uptime * ($window - $missed)) / $window; // Calc earning $one_day_ago = Carbon::now('UTC')->subHours(24); $daily_earning = DB::select(" SELECT bid_self_staked_amount - FROM all_node_data - WHERE public_key = '$a' + FROM all_node_data2 + WHERE public_key = '$p' AND created_at < '$one_day_ago' ORDER BY era_id DESC LIMIT 1 @@ -661,21 +767,21 @@ function($x, $y) { $daily_earning = $address->bid_self_staked_amount - $daily_earning; $daily_earning = $daily_earning < 0 ? 0 : $daily_earning; - $earning_day = $nodeHelper->getValidatorRewards($a, 'day'); - $earning_week = $nodeHelper->getValidatorRewards($a, 'week'); - $earning_month = $nodeHelper->getValidatorRewards($a, 'month'); - $earning_year = $nodeHelper->getValidatorRewards($a, 'year'); + $earning_day = $nodeHelper->getValidatorRewards($p, 'day'); + $earning_week = $nodeHelper->getValidatorRewards($p, 'week'); + $earning_month = $nodeHelper->getValidatorRewards($p, 'month'); + $earning_year = $nodeHelper->getValidatorRewards($p, 'year'); - $return["addresses"][$a] = array( + $return["addresses"][$p] = array( "stake_amount" => $address->bid_total_staked_amount, "delegators" => $address->bid_delegators_count, - "uptime" => $address->uptime, + "uptime" => $historical_performance, "update_responsiveness" => 100, - "peers" => $address->peers, + "peers" => (int)($address->peers), "daily_earning" => $daily_earning, - "total_eras" => $total_eras, + "total_eras" => $current_era_id - $total_eras, "eras_sinse_bad_mark" => $eras_sinse_bad_mark, - "total_bad_marks" => count($total_bad_marks ?? array()), + "total_bad_marks" => $total_bad_marks, "validator_rewards" => array( "day" => $earning_day, "week" => $earning_week, @@ -705,7 +811,7 @@ public function getMyEras() { // Get current era $current_era_id = DB::select(" SELECT era_id - FROM all_node_data + FROM all_node_data2 ORDER BY era_id DESC LIMIT 1 "); @@ -741,9 +847,8 @@ public function getMyEras() { // get addresses data $addresses = DB::select(" SELECT - a.public_key, - a.historical_performance AS uptime - FROM all_node_data AS a + a.public_key, a.uptime + FROM all_node_data2 AS a JOIN user_addresses AS b ON a.public_key = b.public_address_node JOIN users AS c @@ -756,51 +861,92 @@ public function getMyEras() { $addresses = array(); } + // get settings + $voting_eras_to_vote = DB::select(" + SELECT value + FROM settings + WHERE name = 'voting_eras_to_vote' + "); + $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); + $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); + + $uptime_calc_size = DB::select(" + SELECT value + FROM settings + WHERE name = 'uptime_calc_size' + "); + $uptime_calc_size = $uptime_calc_size[0] ?? array(); + $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); + $past_era = $current_era_id - $uptime_calc_size; + + if ($past_era < 1) { + $past_era = 1; + } + // for each member's node address foreach ($addresses as $address) { - $a = $address->public_key ?? ''; - - $eras_sinse_bad_mark = DB::select(" - SELECT a.era_id - FROM all_node_data AS a - JOIN user_addresses AS b - ON a.public_key = b.public_address_node - WHERE a.public_key = '$a' - AND a.bad_mark = 1 - ORDER BY era_id DESC - LIMIT 1 - "); - $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; - $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; + $p = $address->public_key ?? ''; $total_bad_marks = DB::select(" - SELECT bad_mark - FROM all_node_data - WHERE bad_mark = 1 - AND public_key = '$a' + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$p' + AND era_id > $past_era + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) + ORDER BY era_id DESC "); + $eras_sinse_bad_mark = $total_bad_marks[0] ?? array(); + $eras_sinse_bad_mark = $current_era_id - (int)($eras_sinse_bad_mark->era_id ?? 0); + $total_bad_marks = count((array)$total_bad_marks); + $eras_active = DB::select(" SELECT era_id - FROM all_node_data - WHERE public_key = '$a' + FROM all_node_data2 + WHERE public_key = '$p' ORDER BY era_id ASC LIMIT 1 "); $eras_active = (int)($eras_active[0]->era_id ?? 0); - $eras_active = $current_era_id - $eras_active; - $return["addresses"][$a] = array( - "uptime" => $address->uptime, - "eras_active" => $eras_active, + // Calculate historical_performance from past $uptime_calc_size eras + $missed = 0; + $in_current_eras = DB::select(" + SELECT in_current_era + FROM all_node_data2 + WHERE public_key = '$p' + ORDER BY era_id DESC + LIMIT $uptime_calc_size + "); + + $in_current_eras = $in_current_eras ? $in_current_eras : array(); + $window = $in_current_eras ? count($in_current_eras) : 0; + + foreach ($in_current_eras as $c) { + $in = (bool)($c->in_current_era ?? 0); + + if (!$in) { + $missed += 1; + } + } + + $uptime = (float)($address->uptime ?? 0); + $historical_performance = ($uptime * ($window - $missed)) / $window; + + $return["addresses"][$p] = array( + "uptime" => $historical_performance, + "eras_active" => $current_era_id - $eras_active, "eras_sinse_bad_mark" => $eras_sinse_bad_mark, - "total_bad_marks" => count($total_bad_marks ?? array()) + "total_bad_marks" => $total_bad_marks ); } // get eras table data - $era_minus_360 = $current_era_id - 360; + $era_minus_360 = $current_era_id - $uptime_calc_size; if ($era_minus_360 < 1) { $era_minus_360 = 1; @@ -809,10 +955,9 @@ public function getMyEras() { $eras = DB::select(" SELECT a.public_key, a.era_id, a.created_at, - a.in_curent_era, a.in_auction, - a.bid_inactive, a.bad_mark, - a.uptime - FROM all_node_data AS a + a.in_current_era, a.in_auction, + a.bid_inactive, a.uptime + FROM all_node_data2 AS a JOIN user_addresses ON a.public_key = user_addresses.public_address_node JOIN users @@ -888,7 +1033,7 @@ public function getVerifiedMembers(Request $request) { $current_era_id = DB::select(" SELECT era_id - FROM all_node_data + FROM all_node_data2 ORDER BY era_id DESC LIMIT 1 "); @@ -899,7 +1044,7 @@ public function getVerifiedMembers(Request $request) { a.public_key, c.id, c.pseudonym, c.node_status, d.extra_status - FROM all_node_data AS a + FROM all_node_data2 AS a JOIN user_addresses AS b ON a.public_key = b.public_address_node JOIN users AS c @@ -1872,16 +2017,34 @@ public function vote($id, Request $request) $addresses = array(); } + // get settings + $voting_eras_to_vote = DB::select(" + SELECT value + FROM settings + WHERE name = 'voting_eras_to_vote' + "); + $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); + $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); + $past_era = $current_era_id - $voting_eras_to_vote; + + if ($past_era < 1) { + $past_era = 1; + } + foreach ($addresses as $address) { + $p = $address->public_address_node ?? ''; $stable_check = DB::select(" - SELECT stable - FROM all_node_data - WHERE public_key = '$address' - ORDER BY era_id DESC - LIMIT 1 + SELECT uptime + FROM all_node_data2 + WHERE public_key = '$p' + AND era_id > $past_era + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) "); - if ((bool)($stable_check[0]->stable ?? 0)) { + if (!$stable_check) { $stable = true; } } @@ -2030,7 +2193,7 @@ public function getMembers(Request $request) { $current_era_id = DB::select(" SELECT era_id - FROM all_node_data + FROM all_node_data2 ORDER BY era_id DESC LIMIT 1 "); @@ -2041,13 +2204,11 @@ public function getMembers(Request $request) 'users.pseudonym', 'users.created_at', 'user_addresses.node_verified_at', - 'all_node_data.public_key', - 'all_node_data.uptime', - 'all_node_data.historical_performance', - 'all_node_data.stable', - 'all_node_data.bid_delegators_count', - 'all_node_data.bid_delegation_rate', - 'all_node_data.bid_total_staked_amount' + 'all_node_data2.public_key', + 'all_node_data2.uptime', + 'all_node_data2.bid_delegators_count', + 'all_node_data2.bid_delegation_rate', + 'all_node_data2.bid_total_staked_amount' ) ->join( 'user_addresses', @@ -2056,14 +2217,14 @@ public function getMembers(Request $request) 'users.id' ) ->join( - 'all_node_data', - 'all_node_data.public_key', + 'all_node_data2', + 'all_node_data2.public_key', '=', 'user_addresses.public_address_node' ) ->where([ 'users.banned' => 0, - 'all_node_data.era_id' => $current_era_id + 'all_node_data2.era_id' => $current_era_id ]) ->get(); @@ -2300,24 +2461,24 @@ public function getMemberDetail($id, Request $request) $current_era_id = DB::select(" SELECT era_id - FROM all_node_data + FROM all_node_data2 ORDER BY era_id DESC LIMIT 1 "); $current_era_id = (int)($current_era_id[0]->era_id ?? 0); - $response = DB::select(" + $response = DB::select(" SELECT a.public_address_node, a.node_verified_at, b.email, b.first_name, b.last_name, b.created_at, b.kyc_verified_at, b.entity_name, - c.historical_performance AS uptime, c.bid_delegators_count, + c.uptime, c.bid_delegators_count, c.bid_delegation_rate, c.bid_self_staked_amount, c.bid_total_staked_amount, d.casper_association_kyc_hash, d.blockchain_name, d.blockchain_desc FROM user_addresses AS a JOIN users AS b ON a.user_id = b.id - JOIN all_node_data AS c + JOIN all_node_data2 AS c ON a.public_address_node = c.public_key JOIN profile AS d ON b.id = d.user_id @@ -2719,7 +2880,7 @@ public function getListNodes(Request $request) $current_era_id = DB::select(" SELECT era_id - FROM all_node_data + FROM all_node_data2 ORDER BY era_id DESC LIMIT 1 "); @@ -2729,12 +2890,11 @@ public function getListNodes(Request $request) SELECT a.id AS user_id, a.pseudonym, b.public_address_node, - c.stable, c.bad_mark, d.blockchain_name, d.blockchain_desc FROM users AS a JOIN user_addresses AS b ON a.id = b.user_id - JOIN all_node_data AS c + JOIN all_node_data2 AS c ON b.public_address_node = c.public_key JOIN profile AS d ON a.id = d.user_id @@ -2777,7 +2937,7 @@ public function infoDashboard() $nodeInfo = DB::select(" SELECT * - FROM all_node_data + FROM all_node_data2 WHERE public_key = '$lower_address' ORDER BY era_id DESC LIMIT 1 @@ -2806,7 +2966,7 @@ public function getEarningByNode($node) $nodeInfo = DB::select(" SELECT * - FROM all_node_data + FROM all_node_data2 WHERE public_key = '$node' ORDER BY era_id DESC LIMIT 1 @@ -2817,7 +2977,7 @@ public function getEarningByNode($node) $one_day_ago = Carbon::now('UTC')->subHours(24); $daily_earningObject = DB::select(" SELECT bid_self_staked_amount - FROM all_node_data + FROM all_node_data2 WHERE public_key = '$node' AND created_at < '$one_day_ago' ORDER BY era_id DESC diff --git a/database/migrations/2022_10_10_145105_create_all_node_data2.php b/database/migrations/2022_10_10_145105_create_all_node_data2.php new file mode 100644 index 00000000..1565b9be --- /dev/null +++ b/database/migrations/2022_10_10_145105_create_all_node_data2.php @@ -0,0 +1,62 @@ +id(); + $table->string('public_key'); + $table->integer('era_id'); + + // Make uptime + $table->float('uptime')->nullable(); + + // From auction object + $table->bigInteger('current_era_weight')->default(0); + $table->bigInteger('next_era_weight')->default(0); + + // Found in auction object + $table->tinyInteger('in_current_era')->default(0); + $table->tinyInteger('in_next_era')->default(0); + $table->tinyInteger('in_auction')->default(0); + + // Auction bid details + $table->integer('bid_delegators_count')->nullable(); + $table->bigInteger('bid_delegation_rate')->nullable(); + $table->tinyInteger('bid_inactive')->default(0); + $table->bigInteger('bid_self_staked_amount')->default(0); + $table->bigInteger('bid_delegators_staked_amount')->default(0); + $table->bigInteger('bid_total_staked_amount')->default(0); + + // the following fields come from port 8888 when/if discovered + $table->integer('port8888_peers')->nullable(); + $table->integer('port8888_era_id')->nullable(); + $table->integer('port8888_block_height')->nullable(); + $table->string('port8888_build_version')->nullable(); + $table->string('port8888_next_upgrade')->nullable(); + + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('all_node_data2'); + } +} From 26418c251dc207c5b2a0e76c65a644782266dda8 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 11 Oct 2022 12:17:22 -0400 Subject: [PATCH 012/162] all_node_data2 update --- app/Console/Commands/HistoricalData.php | 2 +- .../Controllers/Api/V1/UserController.php | 24 ------------------- 2 files changed, 1 insertion(+), 25 deletions(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index 28a2be75..c5b8a5de 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -44,7 +44,7 @@ public function handle() 011117189c666f81c5160cd610ee383dc9b2d0361f004934754d39752eedc64957 34 seconds per era - 47 seconds per era + 85 seconds per era */ $get_block = 'casper-client get-block '; diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 82f285b2..16eaaaff 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -304,11 +304,6 @@ function($x, $y) { "); $uptime_calc_size = $uptime_calc_size[0] ?? array(); $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); - $past_era = $current_era_id - $uptime_calc_size; - - if ($past_era < 1) { - $past_era = 1; - } // for each address belonging to a user foreach ($addresses as $address) { @@ -318,7 +313,6 @@ function($x, $y) { SELECT era_id FROM all_node_data2 WHERE public_key = '$p' - AND era_id > $past_era AND ( in_current_era = 0 OR bid_inactive = 1 @@ -459,11 +453,6 @@ public function getMembershipPage() { "); $uptime_calc_size = $uptime_calc_size[0] ?? array(); $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); - $past_era = $current_era_id - $uptime_calc_size; - - if ($past_era < 1) { - $past_era = 1; - } foreach ($addresses as $address) { $p = $address->public_key ?? ''; @@ -472,7 +461,6 @@ public function getMembershipPage() { SELECT era_id FROM all_node_data2 WHERE public_key = '$p' - AND era_id > $past_era AND ( in_current_era = 0 OR bid_inactive = 1 @@ -693,11 +681,6 @@ function($x, $y) { "); $uptime_calc_size = $uptime_calc_size[0] ?? array(); $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); - $past_era = $current_era_id - $uptime_calc_size; - - if ($past_era < 1) { - $past_era = 1; - } // for each member's node address foreach ($addresses as $address) { @@ -707,7 +690,6 @@ function($x, $y) { SELECT era_id FROM all_node_data2 WHERE public_key = '$p' - AND era_id > $past_era AND ( in_current_era = 0 OR bid_inactive = 1 @@ -877,11 +859,6 @@ public function getMyEras() { "); $uptime_calc_size = $uptime_calc_size[0] ?? array(); $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); - $past_era = $current_era_id - $uptime_calc_size; - - if ($past_era < 1) { - $past_era = 1; - } // for each member's node address foreach ($addresses as $address) { @@ -891,7 +868,6 @@ public function getMyEras() { SELECT era_id FROM all_node_data2 WHERE public_key = '$p' - AND era_id > $past_era AND ( in_current_era = 0 OR bid_inactive = 1 From 24105ef29b64b4126f0b46dde96c4482f061f7f4 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Tue, 11 Oct 2022 15:57:45 -0400 Subject: [PATCH 013/162] # User Updates --- app/Console/Commands/HistoricalData.php | 204 ++++++++++++++++++ .../Controllers/Api/V1/UserController.php | 4 +- 2 files changed, 206 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index 1dbaf10d..9edcd2a4 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -12,6 +12,210 @@ use Carbon\Carbon; +/* +*** New Updates +// User +// New dashboard endpoint +Route::get('/users/get-dashboard', [UserController::class, 'getUserDashboard']); + +// New membership page endpoint +Route::get('/users/get-membership-page', [UserController::class, 'getMembershipPage']); + +// New Nodes page endpoint +Route::get('/users/get-nodes-page', [UserController::class, 'getNodesPage']); + +// New My Eras page endpoint +Route::get('/users/get-my-eras', [UserController::class, 'getMyEras']); + +// Admin +// New Nodes page endpoint +Route::get('/users/get-nodes-page', [AdminController::class, 'getNodesPage']); + +// New Settings +$items = [ + 'quorum_rate_ballot' => ($request->quorum_rate_ballot ?? null), + 'uptime_warning' => ($request->uptime_warning ?? null), + 'uptime_probation' => ($request->uptime_probation ?? null), + 'uptime_correction_time' => ($request->uptime_correction_time ?? null), + 'uptime_calc_size' => ($request->uptime_calc_size ?? null), + 'voting_eras_to_vote' => ($request->voting_eras_to_vote ?? null), + 'voting_eras_since_redmark' => ($request->voting_eras_since_redmark ?? null), + 'redmarks_revoke' => ($request->redmarks_revoke ?? null), + 'redmarks_revoke_lookback' => ($request->redmarks_revoke_lookback ?? null), + 'responsiveness_warning' => ($request->responsiveness_warning ?? null), + 'responsiveness_probation' => ($request->responsiveness_probation ?? null) +]; + +getUserDashboard() +$return = array( + "node_rank" => 1, + "node_rank_total" => 100, + "total_stake" => 123456, + "total_self_stake" => 1234, + "total_delegators" => 12, + "uptime" => 99.7, + "eras_active" => 500, + "eras_sinse_bad_mark" => 4877, + "total_bad_marks" => 1, + "update_responsiveness" => 100, + "peers" => 0, + "total_members" => 10, + "verified_members" => 4, + "association_members" => array( + array( + "public_key" => 01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80 + "id" => 2, + "pseudonym" => ghost + "node_status" => Online + "status" => approved + "extra_status" => + ), + array( + "public_key" => 01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60 + "id" => 2, + "pseudonym" => ghost + "node_status" => Online + "status" => approved + "extra_status" => + ), + array( + "public_key" => 0103dd8b2b18ef0b9fd5b7c8e340b104ee4d966f2a167eb1a938963f8c8f699a45 + "id" => 3, + "pseudonym" => user2 + "node_status" => Online + "status" => approved + "extra_status" => + ), + array( + "public_key" => 010427c1d1227c9d2aafe8c06c6e6b276da8dcd8fd170ca848b8e3e8e1038a6dc8 + "id" => 4, + "pseudonym" => user3 + "node_status" => Online + "status" => approved + "extra_status" => Suspended + ), + ), + "ranking" => array( + "01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80" => 1, + "01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60" => 2, + "0103dd8b2b18ef0b9fd5b7c8e340b104ee4d966f2a167eb1a938963f8c8f699a45" => 3, + "010427c1d1227c9d2aafe8c06c6e6b276da8dcd8fd170ca848b8e3e8e1038a6dc8" => 4, + ) +); + +getMembershipPage() +$return = array( + "node_status" => "Online", + "kyc_status" => "Verified", + "uptime" => array( + 01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80 => 99.7, + 01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60 => 94.3, + ), + "avg_uptime" => 99.1, + "total_eras" => 3000, + "eras_sinse_bad_mark" => 4877, + "total_bad_marks" => 1, + "update_responsiveness" => 100, + "peers" => 0 +); + +getNodesPage() +$return = array( + "mbs" => 257036, + "ranking" => array( + "01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80" => 1, + "01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60" => 2, + "0103dd8b2b18ef0b9fd5b7c8e340b104ee4d966f2a167eb1a938963f8c8f699a45" => 3, + "010427c1d1227c9d2aafe8c06c6e6b276da8dcd8fd170ca848b8e3e8e1038a6dc8" => 4, + ). + "addresses" => array( + "01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80" => array( + "stake_amount" => 123456, + "delegators" => 12, + "uptime" => 99.7, + "update_responsiveness" => 100, + "peers" => 0, + "daily_earning" => 1234, + "total_eras" => 3000, + "eras_sinse_bad_mark" => 4877, + "total_bad_marks" => 0, + "validator_rewards" => array( + "day" => array(), + "week" => array(), + "month" => array(), + "year" => array() + ) + ), + "01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60" => array( + "stake_amount" => 4444, + "delegators" => 4, + "uptime" => 94.3, + "update_responsiveness" => 100, + "peers" => 0, + "daily_earning" => 123, + "total_eras" => 3000, + "eras_sinse_bad_mark" => 4877, + "total_bad_marks" => 1, + "validator_rewards" => array( + "day" => array(), + "week" => array(), + "month" => array(), + "year" => array() + ) + ) + ) +); + +getMyEras() +$return = array( + "addresses" => array( + "01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80" => array( + "uptime" => 99.7, + "eras_active" => 3000, + "eras_sinse_bad_mark" => 4877, + "total_bad_marks" => 0 + ), + "01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60" => array( + "uptime" => 94.3, + "eras_active" => 3000, + "eras_sinse_bad_mark" => 4877, + "total_bad_marks" => 1 + ) + ), + "column_count" => 3, + "eras" => array( + array( + "era_start_time" => '2022-10-07 00:00:00', + "addresses" => array( + "01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80" => array( + "in_pool" => true, + "rewards" => 123 + ) + ) + ), + array( + "era_start_time" => '2022-10-07 00:00:00', + "addresses" => array( + "01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60" => array( + "in_pool" => true, + "rewards" => 444 + ) + ) + ) + ) +); + +Function that have also changed: +UserController::getVerifiedMembers() +UserController::getMembers() +UserController::getMemberDetail() +UserController::getListNodes() +UserController::getEarningByNode() +MetricController::getMetric() +MetricController::getMetricUser() +MetricController::getMetricUserByNodeName() +*/ + class HistoricalData extends Command { /** diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 039318d1..d8c1d624 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -391,7 +391,7 @@ function($x, $y) { $addresses_count = count($addresses); $addresses_count = $addresses_count ? $addresses_count : 1; - $return["uptime"] = $return["uptime"] / $addresses_count; + $return["uptime"] = round($return["uptime"] / $addresses_count, 2); // remove ranking object. not needed unset($return["ranking"]); @@ -803,7 +803,7 @@ function($x, $y) { info($return); return $this->successResponse($return); } - + public function getMyEras() { $user = auth()->user(); $user_id = $user->id ?? 0; From 96569bca86023f1d709b6f88f7ce66f74e0975de Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 11 Oct 2022 16:18:26 -0400 Subject: [PATCH 014/162] hist update --- app/Console/Commands/HistoricalData.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index 801663b5..5b808360 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -44,7 +44,7 @@ public function handle() 011117189c666f81c5160cd610ee383dc9b2d0361f004934754d39752eedc64957 34 seconds per era - 85 seconds per era + 93 seconds per era */ $get_block = 'casper-client get-block '; @@ -56,8 +56,8 @@ public function handle() $current_era = (int)($json->result->block->header->era_id ?? 0); // $historic_era = 4135; // pre-calculated // $historic_block = 614408; // pre-calculated - $historic_era = 4367; // bookmark - $historic_block = 664698; // bookmark + $historic_era = 5030; // bookmark + $historic_block = 809059; // bookmark while ($current_era > $historic_era) { // first see if we have this era's auction info From 6067ef678acce582cdf3998802a07e31a0479665 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 12 Oct 2022 09:06:22 -0400 Subject: [PATCH 015/162] # API Updates --- app/Console/Commands/HistoricalData.php | 180 ------------------ .../Controllers/Api/V1/AdminController.php | 10 +- .../Controllers/Api/V1/MetricController.php | 28 ++- .../Controllers/Api/V1/UserController.php | 38 ++-- 4 files changed, 36 insertions(+), 220 deletions(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index 94f86d47..32d860c6 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -15,12 +15,6 @@ /* *** New Updates // User -// New dashboard endpoint -Route::get('/users/get-dashboard', [UserController::class, 'getUserDashboard']); - -// New membership page endpoint -Route::get('/users/get-membership-page', [UserController::class, 'getMembershipPage']); - // New Nodes page endpoint Route::get('/users/get-nodes-page', [UserController::class, 'getNodesPage']); @@ -31,180 +25,6 @@ // New Nodes page endpoint Route::get('/users/get-nodes-page', [AdminController::class, 'getNodesPage']); -// New Settings -$items = [ - 'quorum_rate_ballot' => ($request->quorum_rate_ballot ?? null), - 'uptime_warning' => ($request->uptime_warning ?? null), - 'uptime_probation' => ($request->uptime_probation ?? null), - 'uptime_correction_time' => ($request->uptime_correction_time ?? null), - 'uptime_calc_size' => ($request->uptime_calc_size ?? null), - 'voting_eras_to_vote' => ($request->voting_eras_to_vote ?? null), - 'voting_eras_since_redmark' => ($request->voting_eras_since_redmark ?? null), - 'redmarks_revoke' => ($request->redmarks_revoke ?? null), - 'redmarks_revoke_lookback' => ($request->redmarks_revoke_lookback ?? null), - 'responsiveness_warning' => ($request->responsiveness_warning ?? null), - 'responsiveness_probation' => ($request->responsiveness_probation ?? null) -]; - -getUserDashboard() -$return = array( - "node_rank" => 1, - "node_rank_total" => 100, - "total_stake" => 123456, - "total_self_stake" => 1234, - "total_delegators" => 12, - "uptime" => 99.7, - "eras_active" => 500, - "eras_sinse_bad_mark" => 4877, - "total_bad_marks" => 1, - "update_responsiveness" => 100, - "peers" => 0, - "total_members" => 10, - "verified_members" => 4, - "association_members" => array( - array( - "public_key" => 01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80 - "id" => 2, - "pseudonym" => ghost - "node_status" => Online - "status" => approved - "extra_status" => - ), - array( - "public_key" => 01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60 - "id" => 2, - "pseudonym" => ghost - "node_status" => Online - "status" => approved - "extra_status" => - ), - array( - "public_key" => 0103dd8b2b18ef0b9fd5b7c8e340b104ee4d966f2a167eb1a938963f8c8f699a45 - "id" => 3, - "pseudonym" => user2 - "node_status" => Online - "status" => approved - "extra_status" => - ), - array( - "public_key" => 010427c1d1227c9d2aafe8c06c6e6b276da8dcd8fd170ca848b8e3e8e1038a6dc8 - "id" => 4, - "pseudonym" => user3 - "node_status" => Online - "status" => approved - "extra_status" => Suspended - ), - ), - "ranking" => array( - "01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80" => 1, - "01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60" => 2, - "0103dd8b2b18ef0b9fd5b7c8e340b104ee4d966f2a167eb1a938963f8c8f699a45" => 3, - "010427c1d1227c9d2aafe8c06c6e6b276da8dcd8fd170ca848b8e3e8e1038a6dc8" => 4, - ) -); - -getMembershipPage() -$return = array( - "node_status" => "Online", - "kyc_status" => "Verified", - "uptime" => array( - 01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80 => 99.7, - 01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60 => 94.3, - ), - "avg_uptime" => 99.1, - "total_eras" => 3000, - "eras_sinse_bad_mark" => 4877, - "total_bad_marks" => 1, - "update_responsiveness" => 100, - "peers" => 0 -); - -getNodesPage() -$return = array( - "mbs" => 257036, - "ranking" => array( - "01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80" => 1, - "01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60" => 2, - "0103dd8b2b18ef0b9fd5b7c8e340b104ee4d966f2a167eb1a938963f8c8f699a45" => 3, - "010427c1d1227c9d2aafe8c06c6e6b276da8dcd8fd170ca848b8e3e8e1038a6dc8" => 4, - ). - "addresses" => array( - "01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80" => array( - "stake_amount" => 123456, - "delegators" => 12, - "uptime" => 99.7, - "update_responsiveness" => 100, - "peers" => 0, - "daily_earning" => 1234, - "total_eras" => 3000, - "eras_sinse_bad_mark" => 4877, - "total_bad_marks" => 0, - "validator_rewards" => array( - "day" => array(), - "week" => array(), - "month" => array(), - "year" => array() - ) - ), - "01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60" => array( - "stake_amount" => 4444, - "delegators" => 4, - "uptime" => 94.3, - "update_responsiveness" => 100, - "peers" => 0, - "daily_earning" => 123, - "total_eras" => 3000, - "eras_sinse_bad_mark" => 4877, - "total_bad_marks" => 1, - "validator_rewards" => array( - "day" => array(), - "week" => array(), - "month" => array(), - "year" => array() - ) - ) - ) -); - -getMyEras() -$return = array( - "addresses" => array( - "01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80" => array( - "uptime" => 99.7, - "eras_active" => 3000, - "eras_sinse_bad_mark" => 4877, - "total_bad_marks" => 0 - ), - "01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60" => array( - "uptime" => 94.3, - "eras_active" => 3000, - "eras_sinse_bad_mark" => 4877, - "total_bad_marks" => 1 - ) - ), - "column_count" => 3, - "eras" => array( - array( - "era_start_time" => '2022-10-07 00:00:00', - "addresses" => array( - "01026ca707c348ed8012ac6a1f28db031fadd6eb67203501a353b867a08c8b9a80" => array( - "in_pool" => true, - "rewards" => 123 - ) - ) - ), - array( - "era_start_time" => '2022-10-07 00:00:00', - "addresses" => array( - "01031cdce87d5fe53246492f9262932f9eb7421ea54b30da1eca06874fd2a7df60" => array( - "in_pool" => true, - "rewards" => 444 - ) - ) - ) - ) -); - Function that have also changed: UserController::getVerifiedMembers() UserController::getMembers() diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index e7a58e23..8c785744 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -86,7 +86,7 @@ public function getNodesPage() { "peers" => 0, "daily_earning" => 0, "total_eras" => 0, - "eras_sinse_bad_mark" => $current_era_id, + "eras_since_bad_mark" => $current_era_id, "total_bad_marks" => 0, "faling" => 0, "validator_rewards" => array( @@ -207,7 +207,7 @@ function($x, $y) { foreach ($addresses as $address) { $a = $address->public_key ?? ''; - $eras_sinse_bad_mark = DB::select(" + $eras_since_bad_mark = DB::select(" SELECT a.era_id FROM all_node_data AS a JOIN user_addresses AS b @@ -217,8 +217,8 @@ function($x, $y) { ORDER BY era_id DESC LIMIT 1 "); - $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; - $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; + $eras_since_bad_mark = $eras_since_bad_mark[0]->era_id ?? 0; + $eras_since_bad_mark = $current_era_id - $eras_since_bad_mark; $total_bad_marks = DB::select(" SELECT bad_mark @@ -274,7 +274,7 @@ function($x, $y) { "peers" => $address->peers, "daily_earning" => $daily_earning, "total_eras" => $total_eras, - "eras_sinse_bad_mark" => $eras_sinse_bad_mark, + "eras_since_bad_mark" => $eras_since_bad_mark, "total_bad_marks" => count($total_bad_marks ?? array()), "failing" => $failing, "validator_rewards" => array( diff --git a/app/Http/Controllers/Api/V1/MetricController.php b/app/Http/Controllers/Api/V1/MetricController.php index bb10db62..fd89e7d8 100644 --- a/app/Http/Controllers/Api/V1/MetricController.php +++ b/app/Http/Controllers/Api/V1/MetricController.php @@ -28,10 +28,6 @@ public function getMetric(Request $request) $return_object = array(); $user = auth()->user()->load(['pagePermissions']); $user_id = $user->id; - /* - if (Helper::isAccessBlocked($user, 'nodes')) - return $this->successResponse([]); - */ $public_address_node = $request->get('public_address_node'); if (!$public_address_node) { @@ -68,7 +64,7 @@ public function getMetric(Request $request) foreach ($addresses as &$address) { $a = $address->public_key; - $eras_sinse_bad_mark = DB::select(" + $eras_since_bad_mark = DB::select(" SELECT a.era_id FROM all_node_data AS a JOIN user_addresses AS b @@ -78,9 +74,9 @@ public function getMetric(Request $request) ORDER BY era_id DESC LIMIT 1 "); - $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; - $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; - $address->eras_sinse_bad_mark = $eras_sinse_bad_mark; + $eras_since_bad_mark = $eras_since_bad_mark[0]->era_id ?? 0; + $eras_since_bad_mark = $current_era_id - $eras_since_bad_mark; + $address->eras_since_bad_mark = $eras_since_bad_mark; } $monitoring_criteria = DB::select(" @@ -359,7 +355,7 @@ public function getMetricUser($id) foreach ($addresses as &$address) { $a = $address->public_key; - $eras_sinse_bad_mark = DB::select(" + $eras_since_bad_mark = DB::select(" SELECT a.era_id FROM all_node_data AS a JOIN user_addresses AS b @@ -369,9 +365,9 @@ public function getMetricUser($id) ORDER BY era_id DESC LIMIT 1 "); - $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; - $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; - $address->eras_sinse_bad_mark = $eras_sinse_bad_mark; + $eras_since_bad_mark = $eras_since_bad_mark[0]->era_id ?? 0; + $eras_since_bad_mark = $current_era_id - $eras_since_bad_mark; + $address->eras_since_bad_mark = $eras_since_bad_mark; } $monitoring_criteria = DB::select(" @@ -527,7 +523,7 @@ public function getMetricUserByNodeName($node) foreach ($addresses as &$address) { $a = $address->public_key; - $eras_sinse_bad_mark = DB::select(" + $eras_since_bad_mark = DB::select(" SELECT a.era_id FROM all_node_data AS a JOIN user_addresses AS b @@ -537,9 +533,9 @@ public function getMetricUserByNodeName($node) ORDER BY era_id DESC LIMIT 1 "); - $eras_sinse_bad_mark = $eras_sinse_bad_mark[0]->era_id ?? 0; - $eras_sinse_bad_mark = $current_era_id - $eras_sinse_bad_mark; - $address->eras_sinse_bad_mark = $eras_sinse_bad_mark; + $eras_since_bad_mark = $eras_since_bad_mark[0]->era_id ?? 0; + $eras_since_bad_mark = $current_era_id - $eras_since_bad_mark; + $address->eras_since_bad_mark = $eras_since_bad_mark; } $monitoring_criteria = DB::select(" diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 1e5dec58..58bbbb66 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -131,7 +131,7 @@ public function getUserDashboard() { "total_delegators" => 0, "uptime" => 0, "eras_active" => 0, - "eras_sinse_bad_mark" => $current_era_id, + "eras_since_bad_mark" => $current_era_id, "total_bad_marks" => 0, "update_responsiveness" => 100, "peers" => 0, @@ -320,12 +320,12 @@ function($x, $y) { ORDER BY era_id DESC "); - $eras_sinse_bad_mark = $total_bad_marks[0] ?? array(); - $eras_sinse_bad_mark = $current_era_id - (int)($eras_sinse_bad_mark->era_id ?? 0); + $eras_since_bad_mark = $total_bad_marks[0] ?? array(); + $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); $total_bad_marks = count((array)$total_bad_marks); - if ($eras_sinse_bad_mark < $return["eras_sinse_bad_mark"]) { - $return["eras_sinse_bad_mark"] = $eras_sinse_bad_mark; + if ($eras_since_bad_mark < $return["eras_since_bad_mark"]) { + $return["eras_since_bad_mark"] = $eras_since_bad_mark; } $eras_active = DB::select(" @@ -414,7 +414,7 @@ public function getMembershipPage() { "uptime" => array(), "avg_uptime" => 0, "total_eras" => 0, - "eras_sinse_bad_mark" => $current_era_id, + "eras_since_bad_mark" => $current_era_id, "total_bad_marks" => 0, "update_responsiveness" => 100, "peers" => 0 @@ -468,12 +468,12 @@ public function getMembershipPage() { ORDER BY era_id DESC "); - $eras_sinse_bad_mark = $total_bad_marks[0] ?? array(); - $eras_sinse_bad_mark = $current_era_id - (int)($eras_sinse_bad_mark->era_id ?? 0); + $eras_since_bad_mark = $total_bad_marks[0] ?? array(); + $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); $total_bad_marks = count((array)$total_bad_marks); - if ($eras_sinse_bad_mark < $return["eras_sinse_bad_mark"]) { - $return["eras_sinse_bad_mark"] = $eras_sinse_bad_mark; + if ($eras_since_bad_mark < $return["eras_since_bad_mark"]) { + $return["eras_since_bad_mark"] = $eras_since_bad_mark; } $total_eras = DB::select(" @@ -560,7 +560,7 @@ public function getNodesPage() { "peers" => 0, "daily_earning" => 0, "total_eras" => 0, - "eras_sinse_bad_mark" => $current_era_id, + "eras_since_bad_mark" => $current_era_id, "total_bad_marks" => 0, "validator_rewards" => array( "day" => array(), @@ -697,8 +697,8 @@ function($x, $y) { ORDER BY era_id DESC "); - $eras_sinse_bad_mark = $total_bad_marks[0] ?? array(); - $eras_sinse_bad_mark = $current_era_id - (int)($eras_sinse_bad_mark->era_id ?? 0); + $eras_since_bad_mark = $total_bad_marks[0] ?? array(); + $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); $total_bad_marks = count((array)$total_bad_marks); $total_eras = DB::select(" @@ -733,7 +733,7 @@ function($x, $y) { } $uptime = (float)($address->uptime ?? 0); - $historical_performance = ($uptime * ($window - $missed)) / $window; + $historical_performance = round(($uptime * ($window - $missed)) / $window, 2); // Calc earning $one_day_ago = Carbon::now('UTC')->subHours(24); @@ -762,7 +762,7 @@ function($x, $y) { "peers" => (int)($address->peers), "daily_earning" => $daily_earning, "total_eras" => $current_era_id - $total_eras, - "eras_sinse_bad_mark" => $eras_sinse_bad_mark, + "eras_since_bad_mark" => $eras_since_bad_mark, "total_bad_marks" => $total_bad_marks, "validator_rewards" => array( "day" => $earning_day, @@ -805,7 +805,7 @@ public function getMyEras() { "0123456789abcdef" => array( "uptime" => 0, "eras_active" => 0, - "eras_sinse_bad_mark" => $current_era_id, + "eras_since_bad_mark" => $current_era_id, "total_bad_marks" => 0 ) ), @@ -875,8 +875,8 @@ public function getMyEras() { ORDER BY era_id DESC "); - $eras_sinse_bad_mark = $total_bad_marks[0] ?? array(); - $eras_sinse_bad_mark = $current_era_id - (int)($eras_sinse_bad_mark->era_id ?? 0); + $eras_since_bad_mark = $total_bad_marks[0] ?? array(); + $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); $total_bad_marks = count((array)$total_bad_marks); $eras_active = DB::select(" @@ -916,7 +916,7 @@ public function getMyEras() { $return["addresses"][$p] = array( "uptime" => $historical_performance, "eras_active" => $current_era_id - $eras_active, - "eras_sinse_bad_mark" => $eras_sinse_bad_mark, + "eras_since_bad_mark" => $eras_since_bad_mark, "total_bad_marks" => $total_bad_marks ); } From 7e55dfdd5235c7938bb6c78b3051bbe5e29046f1 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 12 Oct 2022 10:29:04 -0400 Subject: [PATCH 016/162] get list nodes methods --- .../Controllers/Api/V1/AdminController.php | 128 +++++++++++++++--- .../Controllers/Api/V1/UserController.php | 116 ++++++++++++++-- 2 files changed, 211 insertions(+), 33 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 8c785744..dfe5eff6 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -1890,25 +1890,117 @@ public function getLockRules() public function getListNodes(Request $request) { - $limit = $request->limit ?? 50; - $node_failing = $request->node_failing ?? ''; - - $nodes = UserAddress::select([ - 'user_addresses.user_id as user_id', - 'user_addresses.public_address_node as public_address_node', - 'user_addresses.is_fail_node as is_fail_node', - 'user_addresses.rank as rank', - ]) - ->join('users', 'users.id', '=', 'user_addresses.user_id') - ->where('users.banned', 0) - ->whereNotNull('user_addresses.public_address_node') - ->where(function ($query) use ($node_failing) { - if ($node_failing == 1) $query->where('user_addresses.is_fail_node', 1); - }) - ->orderBy('user_addresses.rank', 'asc') - ->paginate($limit); + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data2 + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + // define return object + $return = array( + "nodes" => array(), + "ranking" => array(), + "node_rank_total" => 100 + ); - return $this->successResponse($nodes); + // find rank + $ranking = DB::select(" + SELECT + public_key, uptime, + bid_delegators_count, + bid_delegation_rate, + bid_total_staked_amount + FROM all_node_data2 + WHERE era_id = $current_era_id + AND in_current_era = 1 + AND in_next_era = 1 + AND in_auction = 1 + "); + $max_delegators = 0; + $max_stake_amount = 0; + + foreach ($ranking as $r) { + if ((int)$r->bid_delegators_count > $max_delegators) { + $max_delegators = (int)$r->bid_delegators_count; + } + + if ((int)$r->bid_total_staked_amount > $max_stake_amount) { + $max_stake_amount = (int)$r->bid_total_staked_amount; + } + } + + foreach ($ranking as $r) { + $uptime_score = ( + 25 * (float)$r->uptime + ) / 100; + $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; + + $fee_score = ( + 25 * + (1 - ((float)$r->bid_delegation_rate / 100)) + ); + $fee_score = $fee_score < 0 ? 0 : $fee_score; + + $count_score = ( + (float)$r->bid_delegators_count / + $max_delegators + ) * 25; + $count_score = $count_score < 0 ? 0 : $count_score; + + $stake_score = ( + (float)$r->bid_total_staked_amount / + $max_stake_amount + ) * 25; + $stake_score = $stake_score < 0 ? 0 : $stake_score; + + $return["ranking"][$r->public_key] = ( + $uptime_score + + $fee_score + + $count_score + + $stake_score + ); + } + + uasort( + $return["ranking"], + function($x, $y) { + if ($x == $y) { + return 0; + } + + return ($x > $y) ? -1 : 1; + } + ); + + $sorted_ranking = array(); + $i = 1; + + foreach ($return["ranking"] as $public_key => $score) { + $sorted_ranking[$public_key] = $i; + $i += 1; + } + + $return["ranking"] = $sorted_ranking; + $return["node_rank_total"] = count($sorted_ranking); + + $return["nodes"] = DB::select(" + SELECT + a.public_address_node, + b.id, + b.pseudonym, + c.blockchain_name, + c.blockchain_desc + FROM user_addresses AS a + JOIN users AS b + ON a.user_id = b.id + JOIN profile AS c + ON b.id = c.user_id + WHERE b.banned = 0 + "); + // info($nodes); + return $this->successResponse($return); } // Get GraphInfo diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 58bbbb66..4a61855e 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -2870,23 +2870,109 @@ public function getListNodes(Request $request) "); $current_era_id = (int)($current_era_id[0]->era_id ?? 0); - $nodes = DB::select(" + // define return object + $return = array( + "nodes" => array(), + "ranking" => array(), + "node_rank_total" => 100 + ); + + $return["nodes"] = DB::select(" SELECT - a.id AS user_id, a.pseudonym, - b.public_address_node, - d.blockchain_name, d.blockchain_desc - FROM users AS a - JOIN user_addresses AS b - ON a.id = b.user_id - JOIN all_node_data2 AS c - ON b.public_address_node = c.public_key - JOIN profile AS d - ON a.id = d.user_id - WHERE a.banned = 0 - AND a.id = $user_id + a.public_address_node, + b.id, + b.pseudonym, + c.blockchain_name, + c.blockchain_desc + FROM user_addresses AS a + JOIN users AS b + ON a.user_id = b.id + JOIN profile AS c + ON b.id = c.user_id + WHERE b.id = $user_id "); - info($nodes); - return $this->successResponse($nodes); + + // find rank + $ranking = DB::select(" + SELECT + public_key, uptime, + bid_delegators_count, + bid_delegation_rate, + bid_total_staked_amount + FROM all_node_data2 + WHERE era_id = $current_era_id + AND in_current_era = 1 + AND in_next_era = 1 + AND in_auction = 1 + "); + $max_delegators = 0; + $max_stake_amount = 0; + + foreach ($ranking as $r) { + if ((int)$r->bid_delegators_count > $max_delegators) { + $max_delegators = (int)$r->bid_delegators_count; + } + + if ((int)$r->bid_total_staked_amount > $max_stake_amount) { + $max_stake_amount = (int)$r->bid_total_staked_amount; + } + } + + foreach ($ranking as $r) { + $uptime_score = ( + 25 * (float)$r->uptime + ) / 100; + $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; + + $fee_score = ( + 25 * + (1 - ((float)$r->bid_delegation_rate / 100)) + ); + $fee_score = $fee_score < 0 ? 0 : $fee_score; + + $count_score = ( + (float)$r->bid_delegators_count / + $max_delegators + ) * 25; + $count_score = $count_score < 0 ? 0 : $count_score; + + $stake_score = ( + (float)$r->bid_total_staked_amount / + $max_stake_amount + ) * 25; + $stake_score = $stake_score < 0 ? 0 : $stake_score; + + $return["ranking"][$r->public_key] = ( + $uptime_score + + $fee_score + + $count_score + + $stake_score + ); + } + + uasort( + $return["ranking"], + function($x, $y) { + if ($x == $y) { + return 0; + } + + return ($x > $y) ? -1 : 1; + } + ); + + $sorted_ranking = array(); + $i = 1; + + foreach ($return["ranking"] as $public_key => $score) { + $sorted_ranking[$public_key] = $i; + $i += 1; + } + + $return["ranking"] = $sorted_ranking; + $return["node_rank_total"] = count($sorted_ranking); + + return $this->successResponse($return); From 58fecb6f88798f821de1e5648bf7279839e6019e Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 12 Oct 2022 21:17:24 -0400 Subject: [PATCH 017/162] # User API --- app/Http/Controllers/Api/V1/UserController.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 4a61855e..3d2725e6 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -2868,7 +2868,7 @@ public function getListNodes(Request $request) ORDER BY era_id DESC LIMIT 1 "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + $current_era_id = (int) ($current_era_id[0]->era_id ?? 0); // define return object $return = array( @@ -2880,12 +2880,12 @@ public function getListNodes(Request $request) $return["nodes"] = DB::select(" SELECT a.public_address_node, - b.id, + b.id, b.pseudonym, c.blockchain_name, c.blockchain_desc FROM user_addresses AS a - JOIN users AS b + JOIN users AS b ON a.user_id = b.id JOIN profile AS c ON b.id = c.user_id @@ -2975,8 +2975,6 @@ function($x, $y) { return $this->successResponse($return); - - $limit = $request->limit ?? 50; $nodes = UserAddress::select([ From 8443889aa70e35ca49e9503efb0f4478e2a9522a Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 12 Oct 2022 23:35:36 -0400 Subject: [PATCH 018/162] # Updates --- app/Console/Commands/HistoricalData.php | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index 32d860c6..01a61853 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -14,17 +14,6 @@ /* *** New Updates -// User -// New Nodes page endpoint -Route::get('/users/get-nodes-page', [UserController::class, 'getNodesPage']); - -// New My Eras page endpoint -Route::get('/users/get-my-eras', [UserController::class, 'getMyEras']); - -// Admin -// New Nodes page endpoint -Route::get('/users/get-nodes-page', [AdminController::class, 'getNodesPage']); - Function that have also changed: UserController::getVerifiedMembers() UserController::getMembers() From a1abbe6055bfa40d0a892006e1c454d65ccfa7b4 Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 14 Oct 2022 11:21:45 -0400 Subject: [PATCH 019/162] admin all eras endpoints --- app/Console/Commands/HistoricalData.php | 241 ++++++++------- .../Controllers/Api/V1/AdminController.php | 288 +++++++++++++++++- routes/api.php | 6 + 3 files changed, 426 insertions(+), 109 deletions(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index 32d860c6..b299f72f 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -67,8 +67,8 @@ public function handle() 011117189c666f81c5160cd610ee383dc9b2d0361f004934754d39752eedc64957 - 34 seconds per era - 93 seconds per era + 34 seconds per era + 136 seconds per era */ $get_block = 'casper-client get-block '; @@ -80,8 +80,8 @@ public function handle() $current_era = (int)($json->result->block->header->era_id ?? 0); // $historic_era = 4135; // pre-calculated // $historic_block = 614408; // pre-calculated - $historic_era = 5030; // bookmark - $historic_block = 809059; // bookmark + $historic_era = 6708; // bookmark + $historic_block = 1176213; // bookmark while ($current_era > $historic_era) { // first see if we have this era's auction info @@ -136,8 +136,15 @@ public function handle() // get global uptimes from MAKE $global_uptime = $nodeHelper->retrieveGlobalUptime($current_era_id); + + // Set validator key object + $data = array( + "era_id" => $era_id, + "validators" => array() + ); + // loop auction era - info('Looping auction era - Saving uptime, bid, and daily earnings data'); + info('Looping auction era - Appending uptime, bid, and daily earnings data'); foreach ($bids as $b) { $public_key = strtolower($b->public_key ?? 'nill'); $bid = $b->bid ?? array(); @@ -176,38 +183,26 @@ public function handle() } } - DB::table('all_node_data2')->insert( - array( - 'public_key' => $public_key, - 'era_id' => $current_era_id, - 'created_at' => Carbon::now('UTC'), - 'uptime' => $uptime, - 'in_auction' => 1, - 'bid_delegators_count' => $delegators_count, - 'bid_delegation_rate' => $delegation_rate, - 'bid_inactive' => $bid_inactive, - 'bid_self_staked_amount' => $self_staked_amount, - 'bid_delegators_staked_amount' => $delegators_staked_amount, - 'bid_total_staked_amount' => $total_staked_amount - ) + // define DB insert object for all public keys in this era + $data["validators"][$public_key] = array( + "public_key" => $public_key, + "uptime" => $uptime, + "current_era_weight" => 0, + "next_era_weight" => 0, + "in_current_era" => 0, + "in_next_era" => 0, + "in_auction" => 1, + "bid_delegators_count" => $delegators_count, + "bid_delegation_rate" => $delegation_rate, + "bid_inactive" => $bid_inactive, + "bid_self_staked_amount" => $self_staked_amount, + "bid_delegators_staked_amount" => $delegators_staked_amount, + "bid_total_staked_amount" => $total_staked_amount, + "port8888_peers" => 0, + "port8888_build_version" => "", + "port8888_next_upgrade" => "" ); - // save current stake amount to daily earnings table - $earning = new DailyEarning(); - $earning->node_address = $public_key; - $earning->self_staked_amount = (int)$self_staked_amount; - $earning->created_at = Carbon::now('UTC'); - $earning->save(); - - // get difference between current self stake and yesterdays self stake - $get_earning = DailyEarning::where('node_address', $public_key) - ->where('created_at', '>', Carbon::now('UTC')->subHours(24)) - ->orderBy('created_at', 'asc') - ->first(); - - $yesterdays_self_staked_amount = (int)($get_earning->self_staked_amount ?? 0); - $daily_earning = $self_staked_amount - $yesterdays_self_staked_amount; - // look for existing peer by public key for port 8888 data foreach ($port8888_responses as $port8888data) { $our_public_signing_key = strtolower($port8888data['our_public_signing_key'] ?? ''); @@ -222,19 +217,6 @@ public function handle() $chainspec_name = $port8888data['chainspec_name'] ?? 'casper'; $next_upgrade = $port8888data['next_upgrade']['protocol_version'] ?? ''; - DB::table('all_node_data2') - ->where('public_key', $public_key) - ->where('era_id', $current_era_id) - ->update( - array( - 'port8888_peers' => $peer_count, - 'port8888_era_id' => $era_id, - 'port8888_block_height' => $block_height, - 'port8888_build_version' => $build_version, - 'port8888_next_upgrade' => $next_upgrade - ) - ); - break; } } @@ -242,84 +224,131 @@ public function handle() // loop current era $current_validator_weights = $current_era_validators->validator_weights ?? array(); - info('Saving current era validator weights'); + info('Appending current era validator weights'); foreach ($current_validator_weights as $v) { - $public_key = $v->public_key ?? ''; + $public_key = strtolower($v->public_key ?? ''); $weight = (int)($v->weight / 1000000000 ?? 0); - $check = DB::select(" - SELECT public_key, era_id - FROM all_node_data2 - WHERE public_key = '$public_key' - AND era_id = $current_era_id - "); - $check = $check[0] ?? null; + if (isset($data["validators"][$public_key])) { + $data + ["validators"] + [$public_key] + ["current_era_weight"] = $weight; - if (!$check) { - DB::table('all_node_data2')->insert( - array( - 'public_key' => $public_key, - 'era_id' => $current_era_id, - 'current_era_weight' => $weight, - 'in_current_era' => 1, - 'created_at' => Carbon::now('UTC') - ) - ); + $data + ["validators"] + [$public_key] + ["in_current_era"] = 1; } else { - DB::table('all_node_data2') - ->where('public_key', $public_key) - ->where('era_id', $current_era_id) - ->update( - array( - 'current_era_weight' => $weight, - 'in_current_era' => 1 - ) + $data["validators"][$public_key] = array( + "public_key" => $public_key, + "uptime" => 0, + "current_era_weight" => $weight, + "next_era_weight" => 0, + "in_current_era" => 1, + "in_next_era" => 0, + "in_auction" => 0, + "bid_delegators_count" => 0, + "bid_delegation_rate" => 0, + "bid_inactive" => 1, + "bid_self_staked_amount" => 0, + "bid_delegators_staked_amount" => 0, + "bid_total_staked_amount" => 0, + "port8888_peers" => 0, + "port8888_build_version" => "", + "port8888_next_upgrade" => "" ); } } // loop next era $next_validator_weights = $next_era_validators->validator_weights ?? array(); - info('Saving next era validator weights'); + info('Appending next era validator weights'); foreach ($next_validator_weights as $v) { $public_key = $v->public_key ?? ''; $weight = (int)($v->weight / 1000000000 ?? 0); - $check = DB::select(" - SELECT public_key, era_id - FROM all_node_data2 - WHERE public_key = '$public_key' - AND era_id = $current_era_id - "); - $check = $check[0] ?? null; + if (isset($data["validators"][$public_key])) { + $data + ["validators"] + [$public_key] + ["next_era_weight"] = $weight; - if (!$check) { - // info('Saved: '.$public_key); - DB::table('all_node_data2')->insert( - array( - 'public_key' => $public_key, - 'era_id' => $current_era_id, - 'next_era_weight' => $weight, - 'in_next_era' => 1, - 'created_at' => Carbon::now('UTC') - ) - ); + $data + ["validators"] + [$public_key] + ["in_next_era"] = 1; } else { - // info('Updated: '.$public_key); - DB::table('all_node_data2') - ->where('public_key', $public_key) - ->where('era_id', $current_era_id) - ->update( - array( - 'next_era_weight' => $weight, - 'in_next_era' => 1 - ) + $data["validators"][$public_key] = array( + "public_key" => $public_key, + "uptime" => 0, + "current_era_weight" => $weight, + "next_era_weight" => 0, + "in_current_era" => 1, + "in_next_era" => 0, + "in_auction" => 0, + "bid_delegators_count" => 0, + "bid_delegation_rate" => 0, + "bid_inactive" => 1, + "bid_self_staked_amount" => 0, + "bid_delegators_staked_amount" => 0, + "bid_total_staked_amount" => 0, + "port8888_peers" => 0, + "port8888_build_version" => "", + "port8888_next_upgrade" => "" ); } } + // Primary DB insertion (time consuming) + info('Saving validator objects to DB...'); + $created_at = Carbon::now('UTC'); + + foreach ($data["validators"] as $v) { + DB::table('all_node_data2')->insert( + array( + 'public_key' => $v["public_key"], + 'era_id' => $data["era_id"], + 'uptime' => $v["uptime"], + 'current_era_weight' => $v["current_era_weight"], + 'next_era_weight' => $v["next_era_weight"], + 'in_current_era' => $v["in_current_era"], + 'in_next_era' => $v["in_next_era"], + 'in_auction' => $v["in_auction"], + 'bid_delegators_count' => $v["bid_delegators_count"], + 'bid_delegation_rate' => $v["bid_delegation_rate"], + 'bid_inactive' => $v["bid_inactive"], + 'bid_self_staked_amount' => $v["bid_self_staked_amount"], + 'bid_delegators_staked_amount' => $v["bid_delegators_staked_amount"], + 'bid_total_staked_amount' => $v["bid_total_staked_amount"], + 'port8888_peers' => $v["port8888_peers"], + 'port8888_build_version' => $v["port8888_build_version"], + 'port8888_next_upgrade' => $v["port8888_next_upgrade"], + 'created_at' => $created_at + ) + ); + + // save current stake amount to daily earnings table + $earning = new DailyEarning(); + $earning->node_address = $public_key; + $earning->self_staked_amount = (int)$self_staked_amount; + $earning->created_at = Carbon::now('UTC'); + $earning->save(); + + // get difference between current self stake and yesterdays self stake + // $get_earning = DailyEarning::where('node_address', $public_key) + // ->where('created_at', '>', Carbon::now('UTC')->subHours(24)) + // ->orderBy('created_at', 'asc') + // ->first(); + + // $yesterdays_self_staked_amount = (int)($get_earning->self_staked_amount ?? 0); + // $daily_earning = $self_staked_amount - $yesterdays_self_staked_amount; + + info($v["public_key"].' - done'); + } + // find MBS info('Finding MBS for this era'); rsort($MBS_arr); @@ -342,7 +371,7 @@ public function handle() array( 'era_id' => $current_era_id, 'mbs' => $MBS, - 'created_at' => Carbon::now('UTC') + 'created_at' => $created_at ) ); } else { @@ -350,8 +379,8 @@ public function handle() ->where('era_id', $current_era_id) ->update( array( - 'mbs' => $MBS, - 'updated_at' => Carbon::now('UTC') + 'mbs' => $MBS, + 'updated_at' => $created_at ) ); } diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index dfe5eff6..a05ea14a 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -61,6 +61,278 @@ class AdminController extends Controller { + public function allErasUser($id) { + $user = User::where('id', $id)->first(); + + if (!$user || $user->role == 'admin') { + return $this->errorResponse( + __('api.error.not_found'), + Response::HTTP_NOT_FOUND + ); + } + + $user_id = $id; + + // Get current era + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data2 + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + // define return object + $return = array( + "column_count" => 2, + "eras" => array( + array( + "era_start_time" => '', + "addresses" => array( + "0123456789abcdef" => array( + "in_pool" => false, + "rewards" => 0 + ) + ) + ) + ) + ); + + unset($return["eras"][0]); + + // get settings + $voting_eras_to_vote = DB::select(" + SELECT value + FROM settings + WHERE name = 'voting_eras_to_vote' + "); + $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); + $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); + + $uptime_calc_size = DB::select(" + SELECT value + FROM settings + WHERE name = 'uptime_calc_size' + "); + $uptime_calc_size = $uptime_calc_size[0] ?? array(); + $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); + + // get eras table data + $era_minus_360 = $current_era_id - $uptime_calc_size; + + if ($era_minus_360 < 1) { + $era_minus_360 = 1; + } + + $eras = DB::select(" + SELECT + a.public_key, a.era_id, a.created_at, + a.in_current_era, a.in_auction, + a.bid_inactive, a.uptime + FROM all_node_data2 AS a + JOIN user_addresses + ON a.public_key = user_addresses.public_address_node + JOIN users + ON user_addresses.user_id = users.id + WHERE users.id = $user_id + AND era_id > $era_minus_360 + ORDER BY a.era_id DESC + "); + + if (!$eras) { + $eras = array(); + } + + $sorted_eras = array(); + + // for each node address's era + foreach ($eras as $era) { + $era_id = $era->era_id ?? 0; + $era_start_time = $era->created_at ?? ''; + $public_key = $era->public_key; + + if (!isset($sorted_eras[$era_id])) { + $sorted_eras[$era_id] = array( + "era_start_time" => $era_start_time, + "addresses" => array() + ); + } + + $sorted_eras + [$era_id] + ["addresses"] + [$public_key] = array( + "in_pool" => $era->in_auction, + "rewards" => $era->uptime, + ); + } + + $return["eras"] = $sorted_eras; + $column_count = 0; + + foreach ($return["eras"] as $era) { + $count = $era["addresses"] ? count($era["addresses"]) : 0; + + if ($count > $column_count) { + $column_count = $count; + } + } + + $return["column_count"] = $column_count + 1; + + info($return); + return $this->successResponse($return); + } + + public function allEras() { + // Get current era + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data2 + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + // define return object + $return = array( + "addresses" => array( + "0123456789abcdef" => array( + "uptime" => 0, + "eras_active" => 0, + "eras_since_bad_mark" => $current_era_id, + "total_bad_marks" => 0 + ) + ), + "users" => array( + array( + "user_id" => 0, + "name" => "Jason Stone", + "pseudonym" => "jsnstone" + ) + ) + ); + + unset($return["addresses"]["0123456789abcdef"]); + unset($return["eras"][0]); + + // get addresses data + $addresses = DB::select(" + SELECT + a.public_key, a.uptime, b.user_id + FROM all_node_data2 AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.era_id = $current_era_id + "); + + if (!$addresses) { + $addresses = array(); + } + + // get settings + $voting_eras_to_vote = DB::select(" + SELECT value + FROM settings + WHERE name = 'voting_eras_to_vote' + "); + $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); + $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); + + $uptime_calc_size = DB::select(" + SELECT value + FROM settings + WHERE name = 'uptime_calc_size' + "); + $uptime_calc_size = $uptime_calc_size[0] ?? array(); + $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); + + // for each member's node address + foreach ($addresses as $address) { + $p = $address->public_key ?? ''; + + $total_bad_marks = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$p' + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) + ORDER BY era_id DESC + "); + + $eras_since_bad_mark = $total_bad_marks[0] ?? array(); + $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); + $total_bad_marks = count((array)$total_bad_marks); + + $eras_active = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$p' + ORDER BY era_id ASC + LIMIT 1 + "); + + $eras_active = (int)($eras_active[0]->era_id ?? 0); + + // Calculate historical_performance from past $uptime_calc_size eras + $missed = 0; + $in_current_eras = DB::select(" + SELECT in_current_era + FROM all_node_data2 + WHERE public_key = '$p' + ORDER BY era_id DESC + LIMIT $uptime_calc_size + "); + + $in_current_eras = $in_current_eras ? $in_current_eras : array(); + $window = $in_current_eras ? count($in_current_eras) : 0; + + foreach ($in_current_eras as $c) { + $in = (bool)($c->in_current_era ?? 0); + + if (!$in) { + $missed += 1; + } + } + + $uptime = (float)($address->uptime ?? 0); + $historical_performance = ($uptime * ($window - $missed)) / $window; + + $return["addresses"][$p] = array( + "uptime" => $historical_performance, + "eras_active" => $current_era_id - $eras_active, + "eras_since_bad_mark" => $eras_since_bad_mark, + "total_bad_marks" => $total_bad_marks + ); + } + + $users = DB::select(" + SELECT + a.id, a.first_name, a.last_name, a.pseudonym + FROM users AS a + WHERE a.role = 'member' + AND a.has_address = 1 + AND a.banned = 0; + "); + + foreach ($users as $user) { + $return["users"][] = array( + "user_id" => $user->id, + "name" => $user->first_name.' '.$user->last_name, + "pseudonym" => $user->pseudonym, + ); + } + + if (!$users) { + $users = array(); + } + + info($return); + return $this->successResponse($return); + } + public function getNodesPage() { // Get current era $current_era_id = DB::select(" @@ -341,6 +613,7 @@ public function getUsers(Request $request) } } info($users); + return $this->successResponse($users); //// done @@ -393,15 +666,24 @@ public function getUsers(Request $request) public function getUserDetail($id) { $user = User::where('id', $id)->first(); - if (!$user || $user->role == 'admin') - return $this->errorResponse(__('api.error.not_found'), Response::HTTP_NOT_FOUND); + + if (!$user || $user->role == 'admin') { + return $this->errorResponse( + __('api.error.not_found'), + Response::HTTP_NOT_FOUND + ); + } + $user = $user->load(['pagePermissions', 'profile', 'shuftipro', 'shuftiproTemp']); $status = 'Not Verified'; + if ($user->profile && $user->profile->status == 'approved') { $status = 'Verified'; - if ($user->profile->extra_status) + + if ($user->profile->extra_status) { $status = $user->profile->extra_status; + } } $user = $user->load(['profile', 'shuftipro', 'shuftiproTemp']); diff --git a/routes/api.php b/routes/api.php index 5189969a..c0c022be 100644 --- a/routes/api.php +++ b/routes/api.php @@ -121,6 +121,12 @@ // New Nodes page endpoint Route::get('/users/get-nodes-page', [AdminController::class, 'getNodesPage']); + // New Eras page endpoint + Route::get('/users/all-eras', [AdminController::class, 'allEras']); + + // New Eras page endpoint for specific selected user + Route::get('/users/all-eras-user/{id}', [AdminController::class, 'allErasUser'])->where('id', '[0-9]+'); + Route::get('/users', [AdminController::class, 'getUsers']); Route::get('/users/{id}', [AdminController::class, 'getUserDetail'])->where('id', '[0-9]+'); Route::get('/dashboard', [AdminController::class, 'infoDashboard']); From 5138bba3fc794bcfa1a39f0b79c9979e9bca1a87 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Fri, 14 Oct 2022 14:25:47 -0400 Subject: [PATCH 020/162] # Setting Update --- app/Console/Commands/HistoricalData.php | 13 ------------- app/Http/Controllers/Api/V1/AdminController.php | 8 +++++--- app/Http/Controllers/Api/V1/UserController.php | 2 +- 3 files changed, 6 insertions(+), 17 deletions(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index 01a61853..5b808360 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -12,19 +12,6 @@ use Carbon\Carbon; -/* -*** New Updates -Function that have also changed: -UserController::getVerifiedMembers() -UserController::getMembers() -UserController::getMemberDetail() -UserController::getListNodes() -UserController::getEarningByNode() -MetricController::getMetric() -MetricController::getMetricUser() -MetricController::getMetricUserByNodeName() -*/ - class HistoricalData extends Command { /** diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index dfe5eff6..1c1e950c 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -1015,12 +1015,13 @@ public function updateGlobalSettings(Request $request) 'quorum_rate_ballot' => ($request->quorum_rate_ballot ?? null), 'uptime_warning' => ($request->uptime_warning ?? null), 'uptime_probation' => ($request->uptime_probation ?? null), - 'uptime_correction_time' => ($request->uptime_correction_time ?? null), + 'uptime_correction_unit' => ($request->uptime_correction_unit ?? null), + 'uptime_correction_value' => ($request->uptime_correction_value ?? null), 'uptime_calc_size' => ($request->uptime_calc_size ?? null), 'voting_eras_to_vote' => ($request->voting_eras_to_vote ?? null), 'voting_eras_since_redmark' => ($request->voting_eras_since_redmark ?? null), 'redmarks_revoke' => ($request->redmarks_revoke ?? null), - 'redmarks_revoke_lookback' => ($request->redmarks_revoke_lookback ?? null), + 'redmarks_revoke_calc_size' => ($request->redmarks_revoke_calc_size ?? null), 'responsiveness_warning' => ($request->responsiveness_warning ?? null), 'responsiveness_probation' => ($request->responsiveness_probation ?? null) ]; @@ -1033,7 +1034,8 @@ public function updateGlobalSettings(Request $request) $setting->value = $value; $setting->save(); } else { - $setting = new Setting(); + $setting = new Setting(); + $setting->name = $name; $setting->value = $value; $setting->save(); } diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 3d2725e6..238de4af 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -825,7 +825,7 @@ public function getMyEras() { unset($return["addresses"]["0123456789abcdef"]); unset($return["eras"][0]); - + // get addresses data $addresses = DB::select(" SELECT From ab6a39ec3e8fa342973ad77f90d718eba527083c Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 14 Oct 2022 17:10:32 -0400 Subject: [PATCH 021/162] historical data update --- app/Console/Commands/HistoricalData.php | 34 +++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index cd4601c5..2f5f15a7 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -58,6 +58,8 @@ public function handle() 34 seconds per era 136 seconds per era + + ~100 block per era */ $get_block = 'casper-client get-block '; @@ -69,8 +71,36 @@ public function handle() $current_era = (int)($json->result->block->header->era_id ?? 0); // $historic_era = 4135; // pre-calculated // $historic_block = 614408; // pre-calculated - $historic_era = 6708; // bookmark - $historic_block = 1176213; // bookmark + + $historic_era = DB::select(" + SELECT era_id + FROM all_node_data2 + ORDER BY era_id DESC + LIMIT 1 + "); + $historic_era = (int)($historic_era[0]->era_id ?? 0); + info('historic_era: '.$historic_era); + $blocks_per_era = 100; + $historic_block = $blocks_per_era * $historic_era; + info('historic_block: '.$historic_block); + $test_era = 0; + + while ($test_era < $historic_era) { + $json = shell_exec($get_block.$node_arg.'-b '.$historic_block); + $json = json_decode($json); + $test_era = (int)($json->result->block->header->era_id ?? 0); + + if ($test_era < $historic_era) { + $era_diff = $historic_era - $test_era; + + if ($era_diff < 0) { + $blocks_per_era = (int)($blocks_per_era / 2); + } + + $historic_block += ($blocks_per_era * $era_diff); + info('Using historic_block: '.$historic_block); + } + } while ($current_era > $historic_era) { // first see if we have this era's auction info From b338c3ae5578eae03f6ac7e0ca09765ab871981e Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Sun, 16 Oct 2022 12:41:01 -0400 Subject: [PATCH 022/162] # API Updates --- .../Controllers/Api/V1/AdminController.php | 55 +++++++++++++++++-- .../Controllers/Api/V1/ContactController.php | 3 +- 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 23ceaa2d..3692a872 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -41,6 +41,7 @@ use App\Models\VerifyUser; use App\Models\Vote; use App\Models\VoteResult; +use App\Models\ContactRecipient; use App\Services\NodeHelper; @@ -95,7 +96,8 @@ public function allErasUser($id) { ) ) ) - ) + ), + "addresses" => [] ); unset($return["eras"][0]); @@ -124,6 +126,25 @@ public function allErasUser($id) { $era_minus_360 = 1; } + // get addresses data + $addresses = DB::select(" + SELECT + a.public_key + FROM all_node_data2 AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + WHERE a.era_id > $era_minus_360 and a.era_id = $current_era_id and b.user_id = $user_id + ORDER BY a.era_id DESC + "); + + if (!$addresses) { + $addresses = []; + } + + foreach ($addresses as $address) { + $return['addresses'][] = $address->public_key; + } + $eras = DB::select(" SELECT a.public_key, a.era_id, a.created_at, @@ -214,8 +235,8 @@ public function allEras() { ); unset($return["addresses"]["0123456789abcdef"]); - unset($return["eras"][0]); - + unset($return['users']); + // get addresses data $addresses = DB::select(" SELECT @@ -320,7 +341,7 @@ public function allEras() { foreach ($users as $user) { $return["users"][] = array( "user_id" => $user->id, - "name" => $user->first_name.' '.$user->last_name, + "name" => $user->first_name . ' ' . $user->last_name, "pseudonym" => $user->pseudonym, ); } @@ -1280,14 +1301,36 @@ public function getGlobalSettings() { $items = Setting::get(); $settings = []; - if ($items) { foreach ($items as $item) { $settings[$item->name] = $item->value; } } - return $this->successResponse($settings); + $ruleKycNotVerify = LockRules::where('type', 'kyc_not_verify') + ->orderBy('id', 'ASC') + ->select(['id', 'screen', 'is_lock']) + ->get(); + $ruleStatusIsPoor = LockRules::where('type', 'status_is_poor') + ->orderBy('id', 'ASC') + ->select(['id', 'screen', 'is_lock']) + ->get(); + + $membershipAgreementFile = MembershipAgreementFile::first(); + + $contactRecipients = ContactRecipient::orderBy('created_at', 'desc')->get(); + + $data = [ + 'globalSettings' => $settings, + 'lockRules' => [ + 'kyc_not_verify' => $ruleKycNotVerify, + 'status_is_poor' => $ruleStatusIsPoor + ], + 'membershipAgreementFile' => $membershipAgreementFile, + 'contactRecipients' => $contactRecipients + ]; + + return $this->successResponse($data); } // Update Global Settings diff --git a/app/Http/Controllers/Api/V1/ContactController.php b/app/Http/Controllers/Api/V1/ContactController.php index 7e9e2187..494a9b41 100644 --- a/app/Http/Controllers/Api/V1/ContactController.php +++ b/app/Http/Controllers/Api/V1/ContactController.php @@ -44,10 +44,9 @@ public function submitContact(Request $request) public function getContactRecipients(Request $request) { - $limit = $request->limit ?? 50; $sort_key = $request->sort_key ?? 'created_at'; $sort_direction = $request->sort_direction ?? 'desc'; - $contactRecipients = ContactRecipient::orderBy($sort_key, $sort_direction)->paginate($limit); + $contactRecipients = ContactRecipient::orderBy($sort_key, $sort_direction)->get(); return $this->successResponse($contactRecipients); } From 53fe27a365a772939080c1e38a9dc5b66c11af7a Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Sun, 16 Oct 2022 15:34:48 -0400 Subject: [PATCH 023/162] # User API Updates --- .../Controllers/Api/V1/AdminController.php | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 3692a872..867b04d5 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -594,8 +594,8 @@ function($x, $y) { public function getUsers(Request $request) { - $users = DB::select(" - SELECT + /* + SELECT a.id, a.first_name, a.last_name, a.email, a.pseudonym, a.telegram, a.email_verified_at, a.entity_name, a.last_login_at, a.created_at, @@ -616,6 +616,23 @@ public function getUsers(Request $request) ON user_addresses.user_id = a.id JOIN all_node_data AS c ON c.public_key = user_addresses.public_address_node + */ + $users = DB::select(" + SELECT + a.id, a.first_name, a.last_name, a.email, + a.pseudonym, a.telegram, a.email_verified_at, + a.entity_name, a.last_login_at, a.created_at, + a.signature_request_id, a.node_verified_at, + a.member_status, a.kyc_verified_at, + b.dob, b.country_citizenship, b.country_residence, + b.status AS profile_status, b.extra_status, + b.type, b.casper_association_kyc_hash, + b.blockchain_name, b.blockchain_desc + FROM users AS a + LEFT JOIN profile AS b + ON a.id = b.user_id + WHERE a.role = 'member' + ORDER BY a.id asc "); if ($users) { @@ -637,10 +654,6 @@ public function getUsers(Request $request) return $this->successResponse($users); //// done - - - - $limit = $request->limit ?? 50; $sort_key = $request->sort_key ?? 'created_at'; $sort_direction = $request->sort_direction ?? 'desc'; From 4d216e6767168b3dbc653e4e99281beccd4bf094 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Sun, 16 Oct 2022 22:03:00 -0400 Subject: [PATCH 024/162] # Trending API Updates --- app/Http/Controllers/Api/V1/DiscussionController.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Api/V1/DiscussionController.php b/app/Http/Controllers/Api/V1/DiscussionController.php index 6302a923..99223607 100644 --- a/app/Http/Controllers/Api/V1/DiscussionController.php +++ b/app/Http/Controllers/Api/V1/DiscussionController.php @@ -57,8 +57,7 @@ public function getTrending(Request $request) if (Helper::isAccessBlocked($user, 'discussions')) return $this->successResponse(['data' => []]); - $limit = $request->limit ?? 50; - $trendings = Discussion::where('likes', '!=', 0)->where('is_draft', 0)->take(9)->orderBy('likes', 'desc')->paginate($limit); + $trendings = Discussion::where('likes', '!=', 0)->where('is_draft', 0)->take(9)->orderBy('likes', 'desc')->get(); $count = Discussion::where('likes', '!=', 0)->where('is_draft', 0)->orderBy('likes', 'desc')->count(); if ($count >= 9) { return $this->successResponse($trendings); @@ -71,10 +70,8 @@ public function getTrending(Request $request) ->where('is_draft', 0) ->take($remains)->orderBy('id', 'desc')->get(); $trendingArray = $trendings->toArray(); - $trendingArray['data'] = array_merge($trendingArray['data'], $news->toArray()); - return $this->successResponse([ - 'data' => $trendingArray['data'] - ]); + $trendingArray = array_merge($trendingArray, $news->toArray()); + return $this->successResponse($trendingArray); } } From 47cdc7b3ab58379e4ed7b1940e74e05876093dde Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 17 Oct 2022 11:34:56 -0400 Subject: [PATCH 025/162] canVote endpoint --- app/Console/Commands/HistoricalData.php | 2 +- .../Controllers/Api/V1/UserController.php | 108 ++++++++++++++++++ routes/api.php | 3 + 3 files changed, 112 insertions(+), 1 deletion(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index 8bdae6c5..f9a63490 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -89,7 +89,7 @@ public function handle() } } - while ($current_era > $historic_era) { + while ($current_era >= $historic_era) { // first see if we have this era's auction info $node_data = DB::select(" SELECT era_id diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 238de4af..e1e06aed 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1971,6 +1971,114 @@ public function getVoteDetail($id) return $this->successResponse($ballot); } + public function canVote() + { + $user = auth()->user(); + $user_id = $user->id; + + $return = array( + 'voting_eras' => 0, + 'good_standing_eras' => 0, + 'total_active_eras' => 0, + 'can_vote' = false + ); + + // current era + $current_era_id = DB::select(" + SELECT era_id + FROM all_node_data2 + ORDER BY era_id DESC + LIMIT 1 + "); + $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + + // get settings + $voting_eras_to_vote = DB::select(" + SELECT value + FROM settings + WHERE name = 'voting_eras_to_vote' + "); + + // voting_eras + $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); + $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); + $past_era = $current_era_id - $voting_eras_to_vote; + + $return['voting_eras'] = $voting_eras_to_vote; + + $user_addresses = DB::select(" + SELECT public_address_node + FROM user_addresses + WHERE user_id = $user_id + "); + $user_addresses = $user_addresses ?? array(); + + foreach ($user_addresses as $a) { + $p = $a->public_address_node ?? ''; + + // find smallest number of eras since public_key encountered a bad mark + // good_standing_eras + $good_standing_eras = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$p' + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) + ORDER BY era_id DESC + LIMIT 1 + "); + $good_standing_eras = $good_standing_eras[0] ?? array(); + $good_standing_eras = (int)($good_standing_eras->era_id ?? 0); + $good_standing_eras = $current_era_id - $good_standing_eras; + + if ($good_standing_eras < 0) { + $good_standing_eras = 0; + } + + if ($good_standing_eras > $return['good_standing_eras']) { + $return['good_standing_eras'] = $good_standing_eras; + } + + // total_active_eras + $eras = DB::select(" + SELECT count(id) + FROM all_node_data2 + WHERE public_key = '$p' + "); + + $eras = (array)($eras[0]); + $eras = (int)($eras['count(id)'] ?? 0); + + if ($current_era_id - $eras > $return['total_active_eras']) { + $return['total_active_eras'] = $current_era_id - $eras; + } + + // can_vote + $stable_check = DB::select(" + SELECT uptime + FROM all_node_data2 + WHERE public_key = '$p' + AND era_id > $past_era + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) + "); + + if (!$stable_check) { + $stable = true; + } + + if ($stable) { + $return['can_vote'] = true; + } + } + + return $this->successResponse($return); + } + // vote the ballot public function vote($id, Request $request) { diff --git a/routes/api.php b/routes/api.php index c0c022be..e9fe5b1e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -65,6 +65,9 @@ // New My Eras page endpoint Route::get('/users/get-my-eras', [UserController::class, 'getMyEras']); + // New endpoint for User voting eligibility check + Route::get('/users/can-vote', [UserController::class, 'canVote']); + Route::post('/users/verify-email', [AuthController::class, 'verifyEmail']); Route::post('/users/resend-verify-email', [AuthController::class, 'resendVerifyEmail']); Route::post('/users/change-email', [UserController::class, 'changeEmail']); From d9b8a7d99b4ef6d6ef1a7f4c88f10f0a3d5c3fff Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Mon, 17 Oct 2022 11:40:57 -0400 Subject: [PATCH 026/162] # Syntax Error Fix --- app/Http/Controllers/Api/V1/UserController.php | 2 +- routes/api.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index e1e06aed..2c56280f 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1980,7 +1980,7 @@ public function canVote() 'voting_eras' => 0, 'good_standing_eras' => 0, 'total_active_eras' => 0, - 'can_vote' = false + 'can_vote' => false ); // current era diff --git a/routes/api.php b/routes/api.php index e9fe5b1e..a3fbbbf5 100644 --- a/routes/api.php +++ b/routes/api.php @@ -64,7 +64,7 @@ // New My Eras page endpoint Route::get('/users/get-my-eras', [UserController::class, 'getMyEras']); - + // New endpoint for User voting eligibility check Route::get('/users/can-vote', [UserController::class, 'canVote']); From 6047ba098ece20de711a274dbbaf156f761bc44f Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 17 Oct 2022 12:46:57 -0400 Subject: [PATCH 027/162] good standing for voting logic/naming --- .../Controllers/Api/V1/UserController.php | 44 ++++++++----------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 2c56280f..3120ade7 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1977,10 +1977,11 @@ public function canVote() $user_id = $user->id; $return = array( - 'voting_eras' => 0, - 'good_standing_eras' => 0, - 'total_active_eras' => 0, - 'can_vote' => false + 'setting_voting_eras' => 0, + 'setting_good_standing_eras' => 0, + 'good_standing_eras' => 0, + 'total_active_eras' => 0, + 'can_vote' = false ); // current era @@ -1998,13 +1999,19 @@ public function canVote() FROM settings WHERE name = 'voting_eras_to_vote' "); - - // voting_eras $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); - $past_era = $current_era_id - $voting_eras_to_vote; - $return['voting_eras'] = $voting_eras_to_vote; + $voting_eras_since_redmark = DB::select(" + SELECT value + FROM settings + WHERE name = 'voting_eras_since_redmark' + "); + $voting_eras_since_redmark = $voting_eras_since_redmark[0] ?? array(); + $voting_eras_since_redmark = (int)($voting_eras_since_redmark->value ?? 0); + + $return['setting_voting_eras'] = $voting_eras_to_vote; + $return['setting_good_standing_eras'] = $voting_eras_since_redmark; $user_addresses = DB::select(" SELECT public_address_node @@ -2055,23 +2062,10 @@ public function canVote() $return['total_active_eras'] = $current_era_id - $eras; } - // can_vote - $stable_check = DB::select(" - SELECT uptime - FROM all_node_data2 - WHERE public_key = '$p' - AND era_id > $past_era - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - "); - - if (!$stable_check) { - $stable = true; - } - - if ($stable) { + if ( + $return['total_active_eras'] >= $voting_eras_to_vote && + $return['good_standing_eras'] >= $voting_eras_since_redmark + ) { $return['can_vote'] = true; } } From 786db2b4c6c5330622ac6694e3571d5319d1afb9 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Mon, 17 Oct 2022 12:53:42 -0400 Subject: [PATCH 028/162] # Syntax Error Fix --- app/Http/Controllers/Api/V1/UserController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 3120ade7..d1ef6b40 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1981,7 +1981,7 @@ public function canVote() 'setting_good_standing_eras' => 0, 'good_standing_eras' => 0, 'total_active_eras' => 0, - 'can_vote' = false + 'can_vote' => false ); // current era From 1f9de8eeaed9d854e556af6b5de148a1cd075dd5 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 17 Oct 2022 12:57:17 -0400 Subject: [PATCH 029/162] vote logic fix --- .../Controllers/Api/V1/UserController.php | 46 +++++++++++++++---- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 3120ade7..656c3bbb 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -2055,7 +2055,7 @@ public function canVote() WHERE public_key = '$p' "); - $eras = (array)($eras[0]); + $eras = (array)($eras[0] ?? array()); $eras = (int)($eras['count(id)'] ?? 0); if ($current_era_id - $eras > $return['total_active_eras']) { @@ -2110,26 +2110,54 @@ public function vote($id, Request $request) "); $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); - $past_era = $current_era_id - $voting_eras_to_vote; - if ($past_era < 1) { - $past_era = 1; - } + $voting_eras_since_redmark = DB::select(" + SELECT value + FROM settings + WHERE name = 'voting_eras_since_redmark' + "); + $voting_eras_since_redmark = $voting_eras_since_redmark[0] ?? array(); + $voting_eras_since_redmark = (int)($voting_eras_since_redmark->value ?? 0); foreach ($addresses as $address) { $p = $address->public_address_node ?? ''; - $stable_check = DB::select(" - SELECT uptime + + // find smallest number of eras since public_key encountered a bad mark + // good_standing_eras + $good_standing_eras = DB::select(" + SELECT era_id FROM all_node_data2 WHERE public_key = '$p' - AND era_id > $past_era AND ( in_current_era = 0 OR bid_inactive = 1 ) + ORDER BY era_id DESC + LIMIT 1 "); + $good_standing_eras = $good_standing_eras[0] ?? array(); + $good_standing_eras = (int)($good_standing_eras->era_id ?? 0); + $good_standing_eras = $current_era_id - $good_standing_eras; + + if ($good_standing_eras < 0) { + $good_standing_eras = 0; + } - if (!$stable_check) { + // total_active_eras + $total_active_eras = DB::select(" + SELECT count(id) + FROM all_node_data2 + WHERE public_key = '$p' + "); + + $total_active_eras = (array)($total_active_eras[0] ?? array()); + $total_active_eras = (int)($total_active_eras['count(id)'] ?? 0); + $total_active_eras = $current_era_id - $total_active_eras; + + if ( + $total_active_eras >= $voting_eras_to_vote && + $good_standing_eras >= $voting_eras_since_redmark + ) { $stable = true; } } From 619a343ff0fe3d633f6e743387054501c2cfe6e9 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Mon, 17 Oct 2022 13:34:51 -0400 Subject: [PATCH 030/162] # Metric User --- .../Controllers/Api/V1/MetricController.php | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/app/Http/Controllers/Api/V1/MetricController.php b/app/Http/Controllers/Api/V1/MetricController.php index fd89e7d8..5cafae94 100644 --- a/app/Http/Controllers/Api/V1/MetricController.php +++ b/app/Http/Controllers/Api/V1/MetricController.php @@ -368,6 +368,34 @@ public function getMetricUser($id) $eras_since_bad_mark = $eras_since_bad_mark[0]->era_id ?? 0; $eras_since_bad_mark = $current_era_id - $eras_since_bad_mark; $address->eras_since_bad_mark = $eras_since_bad_mark; + + $eras_active = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$a' + ORDER BY era_id ASC + LIMIT 1 + "); + $eras_active = (int) ($eras_active[0]->era_id ?? 0); + if ($current_era_id - $eras_active > 0) { + $address->eras_active = $current_era_id - $eras_active; + } else { + $address->eras_active = 0; + } + + $total_bad_marks = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$a' + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) + ORDER BY era_id DESC + "); + $address->total_bad_marks = count((array)$total_bad_marks); + + $address->update_responsiveness = 100; } $monitoring_criteria = DB::select(" From 2af6a0c6aa8b83590c0759123cb683b52b4e162f Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Tue, 18 Oct 2022 09:11:46 -0400 Subject: [PATCH 031/162] # Update --- app/Http/Controllers/Api/V1/UserController.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 9dfedb2d..9ab183dd 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -2314,6 +2314,7 @@ public function getMembers(Request $request) $members = DB::table('users') ->select( + 'users.id', 'users.pseudonym', 'users.created_at', 'user_addresses.node_verified_at', @@ -2357,7 +2358,7 @@ public function getMembers(Request $request) foreach ($members as &$member) { $uptime_score = ( ($request->uptime ?? 0) * - (float)$member->historical_performance + (float) ($member->historical_performance ?? 0) ) / 100; $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; @@ -2604,9 +2605,6 @@ public function getMemberDetail($id, Request $request) return $this->successResponse($response); //// done - - - $user = User::where('id', $id)->first(); if (!$user || $user->role == 'admin') { From 2d04c044ad69c4cd0a51aec9db12a6ec533cd611 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 18 Oct 2022 10:17:15 -0400 Subject: [PATCH 032/162] getMemberDetail revert --- app/Http/Controllers/Api/V1/UserController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 9ab183dd..71d544ec 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -2597,12 +2597,12 @@ public function getMemberDetail($id, Request $request) JOIN profile AS d ON b.id = d.user_id WHERE ( - a.public_address_node = '$public_address_node' AND + b.id = $id AND c.era_id = $current_era_id - ) OR b.id = $id + ) "); info($response); - return $this->successResponse($response); + // return $this->successResponse($response); //// done $user = User::where('id', $id)->first(); From 6871e9b97e5cd50371f76d0de08cb45dae01c487 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 18 Oct 2022 10:18:59 -0400 Subject: [PATCH 033/162] getMemberDetail update --- app/Http/Controllers/Api/V1/UserController.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 71d544ec..5e96b71e 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -2597,8 +2597,11 @@ public function getMemberDetail($id, Request $request) JOIN profile AS d ON b.id = d.user_id WHERE ( - b.id = $id AND - c.era_id = $current_era_id + b.id = $id AND + c.era_id = $current_era_id + ) OR ( + c.public_key = '$public_address_node' AND + c.era_id = $current_era_id ) "); info($response); From dcdceb274791339e302727f322ff1fffff1b2b53 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 18 Oct 2022 10:56:51 -0400 Subject: [PATCH 034/162] askjsdh --- app/Http/Controllers/Api/V1/UserController.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 5e96b71e..ec28dd9e 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -425,12 +425,12 @@ public function getMembershipPage() { a.public_key, a.uptime, a.port8888_peers AS peers, a.bid_inactive, a.in_current_era, - c.status AS kyc_status + c.kyc_verified_at AS kyc_status FROM all_node_data2 AS a JOIN user_addresses AS b ON a.public_key = b.public_address_node - LEFT JOIN shuftipro AS c - ON b.user_id = c.user_id + LEFT JOIN users AS c + ON b.user_id = c.id WHERE a.era_id = $current_era_id AND b.user_id = $user_id "); From 715a405d56808dacaa0a3036fc6e0322a8a88e67 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 18 Oct 2022 11:00:54 -0400 Subject: [PATCH 035/162] shdhhj --- app/Http/Controllers/Api/V1/UserController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index ec28dd9e..664530d0 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -441,7 +441,7 @@ public function getMembershipPage() { if ( isset($addresses[0]) && - $addresses[0]->kyc_status == 'approved' + $addresses[0]->kyc_status ) { $return["kyc_status"] = "Verified"; } From b6c7589f23cc921adda9bcb792588a5991fe6e41 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 18 Oct 2022 11:05:45 -0400 Subject: [PATCH 036/162] getEarningByNode --- .../Controllers/Api/V1/UserController.php | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 664530d0..796178f3 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -528,7 +528,7 @@ public function getMembershipPage() { $addresses_count = $addresses_count ? $addresses_count : 1; $return["avg_uptime"] = $return["avg_uptime"] / $addresses_count; - info($return); + // info($return); return $this->successResponse($return); } @@ -782,7 +782,7 @@ function($x, $y) { "); $return["mbs"] = (int)($mbs[0]->mbs ?? 0); - info($return); + // info($return); return $this->successResponse($return); } @@ -984,7 +984,7 @@ public function getMyEras() { $return["column_count"] = $column_count + 1; - info($return); + // info($return); return $this->successResponse($return); } @@ -1030,7 +1030,7 @@ public function getVerifiedMembers(Request $request) { WHERE d.status = 'approved' AND a.era_id = $current_era_id "); - info($members); + // info($members); return $this->successResponse($members); //// done @@ -1050,7 +1050,7 @@ public function getVerifiedMembers(Request $request) { ->where('profile.status', 'approved') ->whereNotNull('users.public_address_node') ->paginate($limit); - info($data); + // info($data); return $this->successResponse($data); } @@ -2388,7 +2388,7 @@ public function getMembers(Request $request) ); } - info($members); + // info($members); return $this->successResponse($members); //// done @@ -2604,7 +2604,7 @@ public function getMemberDetail($id, Request $request) c.era_id = $current_era_id ) "); - info($response); + // info($response); // return $this->successResponse($response); //// done @@ -3183,9 +3183,17 @@ public function getEarningByNode($node) LIMIT 1 "); $daily_earning = 0; - if ($daily_earningObject && count($daily_earningObject) > 0) $daily_earning = $daily_earningObject[0]->bid_self_staked_amount ?? 0; - if ($nodeInfo) $daily_earning = $nodeInfo->bid_self_staked_amount - $daily_earning; - else $daily_earning = -$daily_earning; + + if ($daily_earningObject && count($daily_earningObject) > 0) { + $daily_earning = $daily_earningObject[0]->bid_self_staked_amount ?? 0; + } + + if ($nodeInfo) { + $daily_earning = $nodeInfo->bid_self_staked_amount - $daily_earning; + } else { + $daily_earning = -$daily_earning; + } + $daily_earning = $daily_earning < 0 ? 0 : $daily_earning; $mbs = DB::select(" From d8d75f24983176fcc41f419b859bae1c051afce8 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 18 Oct 2022 13:19:47 -0400 Subject: [PATCH 037/162] dev onboarding easy code --- .../Controllers/Api/V1/AuthController.php | 42 +++++++++++++++++++ routes/api.php | 3 ++ 2 files changed, 45 insertions(+) diff --git a/app/Http/Controllers/Api/V1/AuthController.php b/app/Http/Controllers/Api/V1/AuthController.php index 0f50f50a..77caa16e 100644 --- a/app/Http/Controllers/Api/V1/AuthController.php +++ b/app/Http/Controllers/Api/V1/AuthController.php @@ -61,6 +61,48 @@ public function testHash() { exit(Hash::make('ledgerleapllc')); } + public function devVerifyNode($address) + { + $query = DB::select(" + SELECT + a.user_id, a.node_verified_at, a.signed_file, a.node_status, + b.id, b.email, b.node_verified_at, b.signed_file, + b.node_status, b.has_verified_address + FROM user_addresses AS a + JOIN users AS b + ON a.user_id = b.id + WHERE a.public_address_node = '$address' + "); + + $user_id = $query[0] ?? array(); + $user_id = $query->user_id ?? 0; + + if ($user_id) { + $update = DB::table('user_addresses') + ->where('public_key', $address) + ->update( + array( + 'node_verified_at' => '2022-03-18 19:26:51', + 'signed_file' => 'https://casper-assoc-portal-dev.s3.us-east-2.amazonaws.com/signatures/db49744f7535b218c20a48cb833da6a1', + 'node_status' => 'Online' + ) + ); + + $update = DB::table('users') + ->where('id', $user_id) + ->update( + array( + 'node_verified_at' => '2022-03-18 19:26:51', + 'signed_file' => 'https://casper-assoc-portal-dev.s3.us-east-2.amazonaws.com/signatures/db49744f7535b218c20a48cb833da6a1', + 'node_status' => 'Online', + 'has_verified_address' => 1, + ) + ); + } + + return $this->metaSuccess(); + } + /** * Auth user function * diff --git a/routes/api.php b/routes/api.php index a3fbbbf5..78cfd42a 100644 --- a/routes/api.php +++ b/routes/api.php @@ -26,6 +26,9 @@ | */ +//// REMOVE +Route::get('/dev-verify-node/{address}', [AuthController::class, 'devVerifyNode'])->where('address', '[0-9a-zA-Z]+'); + Route::namespace('Api')->middleware([])->group(function () { Route::post('hellosign', [HelloSignController::class, 'hellosignHook']); }); From 986b53ecbc0f849648c02d5180a1c95dda91e48c Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 18 Oct 2022 13:20:52 -0400 Subject: [PATCH 038/162] skajefgjkd --- app/Http/Controllers/Api/V1/AuthController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/AuthController.php b/app/Http/Controllers/Api/V1/AuthController.php index 77caa16e..c1945323 100644 --- a/app/Http/Controllers/Api/V1/AuthController.php +++ b/app/Http/Controllers/Api/V1/AuthController.php @@ -95,7 +95,7 @@ public function devVerifyNode($address) 'node_verified_at' => '2022-03-18 19:26:51', 'signed_file' => 'https://casper-assoc-portal-dev.s3.us-east-2.amazonaws.com/signatures/db49744f7535b218c20a48cb833da6a1', 'node_status' => 'Online', - 'has_verified_address' => 1, + 'has_verified_address' => 1 ) ); } From 3613b6e0f776965b495e3c7b8deb057fe7918df4 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 18 Oct 2022 16:46:54 -0400 Subject: [PATCH 039/162] aksjhd --- app/Http/Controllers/Api/V1/AuthController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/AuthController.php b/app/Http/Controllers/Api/V1/AuthController.php index c1945323..97a461de 100644 --- a/app/Http/Controllers/Api/V1/AuthController.php +++ b/app/Http/Controllers/Api/V1/AuthController.php @@ -74,7 +74,7 @@ public function devVerifyNode($address) WHERE a.public_address_node = '$address' "); - $user_id = $query[0] ?? array(); + $query = $query[0] ?? array(); $user_id = $query->user_id ?? 0; if ($user_id) { From e91d60baab32984231b85662d91bb4bf3faf9847 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 18 Oct 2022 16:48:07 -0400 Subject: [PATCH 040/162] kajshdkas --- app/Http/Controllers/Api/V1/AuthController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/AuthController.php b/app/Http/Controllers/Api/V1/AuthController.php index 97a461de..8a632c4a 100644 --- a/app/Http/Controllers/Api/V1/AuthController.php +++ b/app/Http/Controllers/Api/V1/AuthController.php @@ -79,7 +79,7 @@ public function devVerifyNode($address) if ($user_id) { $update = DB::table('user_addresses') - ->where('public_key', $address) + ->where('public_address_node', $address) ->update( array( 'node_verified_at' => '2022-03-18 19:26:51', From fbd455e42fe94bd8564d32bf7ee283533084df08 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 19 Oct 2022 11:15:31 -0400 Subject: [PATCH 041/162] camp-286, historic data timestamp fix --- app/Console/Commands/HistoricalData.php | 15 +++++++++------ app/Http/Controllers/Api/V1/UserController.php | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index f9a63490..0b854b2e 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -71,11 +71,13 @@ public function handle() $historic_block = $blocks_per_era * $historic_era; info('historic_block: '.$historic_block); $test_era = 0; + $timestamp = ''; while ($test_era < $historic_era) { $json = shell_exec($get_block.$node_arg.'-b '.$historic_block); $json = json_decode($json); $test_era = (int)($json->result->block->header->era_id ?? 0); + $timestamp = $json->result->block->header->timestamp ?? ''; if ($test_era < $historic_era) { $era_diff = $historic_era - $test_era; @@ -85,7 +87,7 @@ public function handle() } $historic_block += ($blocks_per_era * $era_diff); - info('Using historic_block: '.$historic_block); + info('Using historic_block: '.$historic_block.' - '.$timestamp); } } @@ -110,6 +112,8 @@ public function handle() $json = json_decode($switch_block); $era_id = $json->result->block->header->era_id ?? 0; $block_hash = $json->result->block->hash ?? ''; + $timestamp = $json->result->block->header->timestamp ?? ''; + $timestamp = Carbon::parse($timestamp)->format('Y-m-d H:i:s'); if ($era_id == $historic_era) { // start timer @@ -310,7 +314,6 @@ public function handle() // Primary DB insertion (time consuming) info('Saving validator objects to DB...'); - $created_at = Carbon::now('UTC'); foreach ($data["validators"] as $v) { DB::table('all_node_data2')->insert( @@ -332,7 +335,7 @@ public function handle() 'port8888_peers' => $v["port8888_peers"], 'port8888_build_version' => $v["port8888_build_version"], 'port8888_next_upgrade' => $v["port8888_next_upgrade"], - 'created_at' => $created_at + 'created_at' => $timestamp ) ); @@ -340,7 +343,7 @@ public function handle() $earning = new DailyEarning(); $earning->node_address = $public_key; $earning->self_staked_amount = (int)$self_staked_amount; - $earning->created_at = Carbon::now('UTC'); + $earning->created_at = $timestamp; $earning->save(); // get difference between current self stake and yesterdays self stake @@ -377,7 +380,7 @@ public function handle() array( 'era_id' => $current_era_id, 'mbs' => $MBS, - 'created_at' => $created_at + 'created_at' => $timestamp ) ); } else { @@ -386,7 +389,7 @@ public function handle() ->update( array( 'mbs' => $MBS, - 'updated_at' => $created_at + 'updated_at' => $timestamp ) ); } diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 796178f3..8e75a27d 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1175,7 +1175,7 @@ public function uploadLetter(Request $request) try { // Validator $validator = Validator::make($request->all(), [ - 'file' => 'required|mimes:pdf,docx,doc,txt,rtf|max:20000', + 'file' => 'required|mimes:pdf,jpeg,jpg,png,txt,rtf|max:200000' ]); if ($validator->fails()) { From 5d71980b0de35a7cb8643296201887c4cedb6a8a Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 19 Oct 2022 11:41:44 -0400 Subject: [PATCH 042/162] hellosign save link instead of file --- .../Api/V1/HelloSignController.php | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/V1/HelloSignController.php b/app/Http/Controllers/Api/V1/HelloSignController.php index 6d8aba0f..3379269a 100644 --- a/app/Http/Controllers/Api/V1/HelloSignController.php +++ b/app/Http/Controllers/Api/V1/HelloSignController.php @@ -15,12 +15,17 @@ class HelloSignController extends Controller public function hellosignHook(Request $request) { $payload = $request->get('json'); - if (!$payload) return "error"; + + if (!$payload) { + return "error"; + } $data = json_decode($payload, true); $api_key = env('HELLOSIGN_API_KEY_HOOK'); - if (!is_array($data)) return "error"; + if (!is_array($data)) { + return "error"; + } // hellosign test check $callback_test = $data['event']['event_type'] ?? ''; @@ -31,8 +36,11 @@ public function hellosignHook(Request $request) $md5_header_check = base64_encode(hash_hmac('md5', $payload, $api_key)); $md5_header = $request->header('Content-MD5'); - if ($md5_header != $md5_header_check) + + if ($md5_header != $md5_header_check) { return "error"; + } + // Valid Request if ( isset($data['event']) && @@ -43,7 +51,13 @@ public function hellosignHook(Request $request) $filepath = 'hellosign/hellosign_' . $signature_request_id . '.pdf'; $client = new \HelloSign\Client($api_key); - $client->getFiles($signature_request_id, $filepath, \HelloSign\SignatureRequest::FILE_TYPE_PDF); + + $sig_link = $client->getFiles( + $signature_request_id, + null, + \HelloSign\SignatureRequest::FILE_TYPE_PDF + ); + info($sig_link); $user = User::where('signature_request_id', $signature_request_id)->first(); @@ -51,6 +65,7 @@ public function hellosignHook(Request $request) $user->hellosign_form = $filepath; $user->save(); } + return "Hello API Event Received"; } } From c4c2a8f9e905503ed4c94d0830d6c95b6f3bf3c0 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 19 Oct 2022 12:49:35 -0400 Subject: [PATCH 043/162] disable hellosign save to disk --- .../Controllers/Api/V1/HelloSignController.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Api/V1/HelloSignController.php b/app/Http/Controllers/Api/V1/HelloSignController.php index 3379269a..901ddd8b 100644 --- a/app/Http/Controllers/Api/V1/HelloSignController.php +++ b/app/Http/Controllers/Api/V1/HelloSignController.php @@ -52,17 +52,17 @@ public function hellosignHook(Request $request) $client = new \HelloSign\Client($api_key); - $sig_link = $client->getFiles( - $signature_request_id, - null, - \HelloSign\SignatureRequest::FILE_TYPE_PDF - ); - info($sig_link); + // $sig_link = $client->getFiles( + // $signature_request_id, + // null, + // \HelloSign\SignatureRequest::FILE_TYPE_PDF + // ); + // info($sig_link); $user = User::where('signature_request_id', $signature_request_id)->first(); if ($user) { - $user->hellosign_form = $filepath; + $user->hellosign_form = ''; $user->save(); } From a249a9e31a3261ce6a1469b7ba6bec7a07a94cda Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 19 Oct 2022 16:03:47 -0400 Subject: [PATCH 044/162] rm logging --- app/Http/Controllers/Api/V1/UserController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 8e75a27d..3671eed6 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -390,7 +390,7 @@ function($x, $y) { // remove ranking object. not needed unset($return["ranking"]); - info($return); + // info($return); return $this->successResponse($return); } From 2f414238ae32adccce1adc8638e9877cd50c4c09 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 20 Oct 2022 10:19:10 -0400 Subject: [PATCH 045/162] rm logging --- app/Http/Controllers/Api/V1/AdminController.php | 8 ++++---- app/Http/Controllers/Api/V1/MetricController.php | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 867b04d5..01b08880 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -201,7 +201,7 @@ public function allErasUser($id) { $return["column_count"] = $column_count + 1; - info($return); + // info($return); return $this->successResponse($return); } @@ -350,7 +350,7 @@ public function allEras() { $users = array(); } - info($return); + // info($return); return $this->successResponse($return); } @@ -588,7 +588,7 @@ function($x, $y) { "); $return["mbs"] = (int)($mbs[0]->mbs ?? 0); - info($return); + // info($return); return $this->successResponse($return); } @@ -650,7 +650,7 @@ public function getUsers(Request $request) $user->membership_status = $status; } } - info($users); + // info($users); return $this->successResponse($users); //// done diff --git a/app/Http/Controllers/Api/V1/MetricController.php b/app/Http/Controllers/Api/V1/MetricController.php index 5cafae94..e55de006 100644 --- a/app/Http/Controllers/Api/V1/MetricController.php +++ b/app/Http/Controllers/Api/V1/MetricController.php @@ -84,7 +84,7 @@ public function getMetric(Request $request) FROM monitoring_criteria "); $return_object['monitoring_criteria'] = $monitoring_criteria; - info($return_object); + // info($return_object); return $this->successResponse($return_object); //// done @@ -403,7 +403,7 @@ public function getMetricUser($id) FROM monitoring_criteria "); $return_object['monitoring_criteria'] = $monitoring_criteria; - info($return_object); + // info($return_object); return $this->successResponse($return_object); //// done @@ -571,7 +571,7 @@ public function getMetricUserByNodeName($node) FROM monitoring_criteria "); $return_object['monitoring_criteria'] = $monitoring_criteria; - info($return_object); + // info($return_object); return $this->successResponse($return_object); //// done From e107445a1f45dc622bfc19b5a1c99736d3a5119b Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 20 Oct 2022 10:21:43 -0400 Subject: [PATCH 046/162] # Round Uptime --- app/Http/Controllers/Api/V1/UserController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 3671eed6..f275c671 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -914,7 +914,7 @@ public function getMyEras() { $historical_performance = ($uptime * ($window - $missed)) / $window; $return["addresses"][$p] = array( - "uptime" => $historical_performance, + "uptime" => round($historical_performance, 2), "eras_active" => $current_era_id - $eras_active, "eras_since_bad_mark" => $eras_since_bad_mark, "total_bad_marks" => $total_bad_marks From 179fa92c3e44916ccf0e9b70036183d1edec5805 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 20 Oct 2022 10:36:55 -0400 Subject: [PATCH 047/162] admin all_node_data2 fix --- .../Controllers/Api/V1/AdminController.php | 85 +++++++++---------- .../Controllers/Api/V1/UserController.php | 24 +++++- 2 files changed, 62 insertions(+), 47 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 01b08880..e14f24bf 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -319,7 +319,11 @@ public function allEras() { } $uptime = (float)($address->uptime ?? 0); - $historical_performance = ($uptime * ($window - $missed)) / $window; + $historical_performance = round( + ($uptime * ($window - $missed)) / $window, + 2, + PHP_ROUND_HALF_UP + ); $return["addresses"][$p] = array( "uptime" => $historical_performance, @@ -358,7 +362,7 @@ public function getNodesPage() { // Get current era $current_era_id = DB::select(" SELECT era_id - FROM all_node_data + FROM all_node_data2 ORDER BY era_id DESC LIMIT 1 "); @@ -404,11 +408,10 @@ public function getNodesPage() { bid_delegators_count, bid_delegation_rate, bid_total_staked_amount - FROM all_node_data - WHERE in_curent_era = 1 - AND in_next_era = 1 - AND in_auction = 1 - AND bad_mark = 0 + FROM all_node_data2 + WHERE in_current_era = 1 + AND in_next_era = 1 + AND in_auction = 1 "); $max_delegators = 0; $max_stake_amount = 0; @@ -481,10 +484,9 @@ function($x, $y) { SELECT a.public_key, a.bid_delegators_count, a.bid_total_staked_amount, a.bid_self_staked_amount, - a.historical_performance AS uptime, - a.port8888_peers AS peers, - a.bid_inactive, a.bad_mark, a.stable - FROM all_node_data AS a + a.uptime, a.bid_inactive, a.in_current_era, + a.port8888_peers AS peers + FROM all_node_data2 AS a JOIN user_addresses AS b ON a.public_key = b.public_address_node JOIN users AS c @@ -500,34 +502,31 @@ function($x, $y) { foreach ($addresses as $address) { $a = $address->public_key ?? ''; - $eras_since_bad_mark = DB::select(" - SELECT a.era_id - FROM all_node_data AS a - JOIN user_addresses AS b - ON a.public_key = b.public_address_node - WHERE a.public_key = '$a' - AND a.bad_mark = 1 + $total_bad_marks = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$p' + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) ORDER BY era_id DESC - LIMIT 1 "); - $eras_since_bad_mark = $eras_since_bad_mark[0]->era_id ?? 0; - $eras_since_bad_mark = $current_era_id - $eras_since_bad_mark; - $total_bad_marks = DB::select(" - SELECT bad_mark - FROM all_node_data - WHERE bad_mark = 1 - AND public_key = '$a' - "); + $eras_since_bad_mark = $total_bad_marks[0] ?? array(); + $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); + $total_bad_marks = count((array)$total_bad_marks); $total_eras = DB::select(" SELECT era_id - FROM all_node_data - WHERE public_key = '$a' + FROM all_node_data2 + WHERE public_key = '$p' ORDER BY era_id ASC LIMIT 1 "); + $total_eras = (int)($total_eras[0]->era_id ?? 0); + $total_eras = (int)($total_eras[0]->era_id ?? 0); $total_eras = $current_era_id - $total_eras; @@ -535,7 +534,7 @@ function($x, $y) { $one_day_ago = Carbon::now('UTC')->subHours(24); $daily_earning = DB::select(" SELECT bid_self_staked_amount - FROM all_node_data + FROM all_node_data2 WHERE public_key = '$a' AND created_at < '$one_day_ago' ORDER BY era_id DESC @@ -551,8 +550,8 @@ function($x, $y) { $earning_year = $nodeHelper->getValidatorRewards($a, 'year'); if ( - $address->bad_mark == 1 || - $address->bid_inactive == 1 + $address->in_current_era == 0 || + $address->bid_inactive == 1 ) { $failing = 1; } else { @@ -743,7 +742,7 @@ public function infoDashboard(Request $request) { $current_era_id = DB::select(" SELECT era_id - FROM all_node_data + FROM all_node_data2 ORDER BY era_id DESC LIMIT 1 "); @@ -778,7 +777,7 @@ public function infoDashboard(Request $request) $total_stake = DB::select(" SELECT SUM(a.bid_total_staked_amount) - FROM all_node_data AS a + FROM all_node_data2 AS a LEFT JOIN user_addresses AS b ON a.public_key = b.public_address_node WHERE a.era_id = $current_era_id @@ -791,7 +790,7 @@ public function infoDashboard(Request $request) $total_delegators = DB::select(" SELECT SUM(a.bid_delegators_count) - FROM all_node_data AS a + FROM all_node_data2 AS a LEFT JOIN user_addresses AS b ON a.public_key = b.public_address_node WHERE a.era_id = $current_era_id @@ -803,9 +802,9 @@ public function infoDashboard(Request $request) // get avg uptime of all user nodes $uptime_nodes = DB::select(" SELECT - SUM(a.historical_performance) AS numerator, - COUNT(a.historical_performance) AS denominator - FROM all_node_data AS a + SUM(a.uptime) AS numerator, + COUNT(a.uptime) AS denominator + FROM all_node_data2 AS a LEFT JOIN user_addresses AS b ON a.public_key = b.public_address_node WHERE era_id = $current_era_id @@ -823,7 +822,7 @@ public function infoDashboard(Request $request) // get max peers $max_peers = DB::select(" SELECT MAX(a.port8888_peers) AS max_peers - FROM all_node_data AS a + FROM all_node_data2 AS a LEFT JOIN user_addresses AS b ON a.public_key = b.public_address_node WHERE era_id = $current_era_id @@ -854,15 +853,15 @@ public function infoDashboard(Request $request) // get failing member nodes $failing_nodes = DB::select(" SELECT - a.bid_inactive, a.bad_mark - FROM all_node_data AS a + a.bid_inactive, a.in_current_era + FROM all_node_data2 AS a JOIN user_addresses AS b ON a.public_key = b.public_address_node WHERE a.era_id = $current_era_id AND b.user_id IS NOT NULL AND ( - a.bid_inactive = 1 OR - a.bad_mark = 1 + a.bid_inactive = 1 OR + a.in_current_era = 0 ) "); diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 3671eed6..7aa0cfe1 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -364,7 +364,11 @@ function($x, $y) { } $uptime = (float)($address->uptime ?? 0); - $historical_performance = ($uptime * ($window - $missed)) / $window; + $historical_performance = round( + ($uptime * ($window - $missed)) / $window, + 2, + PHP_ROUND_HALF_UP + ); if ( array_key_exists($p, $return["ranking"]) && ( @@ -512,7 +516,11 @@ public function getMembershipPage() { } $uptime = (float)($address->uptime ?? 0); - $historical_performance = ($uptime * ($window - $missed)) / $window; + $historical_performance = round( + ($uptime * ($window - $missed)) / $window, + 2, + PHP_ROUND_HALF_UP + ); $return["total_bad_marks"] += $total_bad_marks; $return["peers"] += (int)($address->peers ?? 0); @@ -733,7 +741,11 @@ function($x, $y) { } $uptime = (float)($address->uptime ?? 0); - $historical_performance = round(($uptime * ($window - $missed)) / $window, 2); + $historical_performance = round( + ($uptime * ($window - $missed)) / $window, + 2, + PHP_ROUND_HALF_UP + ); // Calc earning $one_day_ago = Carbon::now('UTC')->subHours(24); @@ -911,7 +923,11 @@ public function getMyEras() { } $uptime = (float)($address->uptime ?? 0); - $historical_performance = ($uptime * ($window - $missed)) / $window; + $historical_performance = round( + ($uptime * ($window - $missed)) / $window, + 2, + PHP_ROUND_HALF_UP + ); $return["addresses"][$p] = array( "uptime" => $historical_performance, From 6af4a3bd563817e9f6f764f3ddd75ced73e4d631 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 20 Oct 2022 13:33:57 -0400 Subject: [PATCH 048/162] metrics and bypassApproveKYC route --- .../Controllers/Api/V1/AdminController.php | 37 ++++++++++++--- .../Controllers/Api/V1/MetricController.php | 47 +++++++++++-------- ..._20_172223_update_users_add_kyc_bypass.php | 30 ++++++++++++ routes/api.php | 3 ++ 4 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 database/migrations/2022_10_20_172223_update_users_add_kyc_bypass.php diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index e14f24bf..b24312d4 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -133,7 +133,8 @@ public function allErasUser($id) { FROM all_node_data2 AS a JOIN user_addresses AS b ON a.public_key = b.public_address_node - WHERE a.era_id > $era_minus_360 and a.era_id = $current_era_id and b.user_id = $user_id + WHERE a.era_id = $current_era_id + AND b.user_id = $user_id ORDER BY a.era_id DESC "); @@ -408,10 +409,10 @@ public function getNodesPage() { bid_delegators_count, bid_delegation_rate, bid_total_staked_amount - FROM all_node_data2 + FROM all_node_data2 WHERE in_current_era = 1 - AND in_next_era = 1 - AND in_auction = 1 + AND in_next_era = 1 + AND in_auction = 1 "); $max_delegators = 0; $max_stake_amount = 0; @@ -525,8 +526,6 @@ function($x, $y) { LIMIT 1 "); - $total_eras = (int)($total_eras[0]->era_id ?? 0); - $total_eras = (int)($total_eras[0]->era_id ?? 0); $total_eras = $current_era_id - $total_eras; @@ -957,6 +956,32 @@ public function getKYC($id) return $this->successResponse($response); } + public function bypassApproveKYC($user_id) + { + $user_id = (int)$user_id; + $now = Carbon::now('UTC'); + + DB::table('users') + ->where('id', $user_id) + ->update( + array( + 'kyc_verified_at' => $now, + 'approve_at' => $now, + 'kyc_bypass_approval' => 1 + ) + ); + + DB::table('profile') + ->where('user_id', $user_id) + ->update( + array( + 'status' => 'approved' + ) + ); + + return $this->metaSuccess(); + } + // get intake public function getIntakes(Request $request) { diff --git a/app/Http/Controllers/Api/V1/MetricController.php b/app/Http/Controllers/Api/V1/MetricController.php index e55de006..36d46616 100644 --- a/app/Http/Controllers/Api/V1/MetricController.php +++ b/app/Http/Controllers/Api/V1/MetricController.php @@ -36,7 +36,7 @@ public function getMetric(Request $request) $current_era_id = DB::select(" SELECT era_id - FROM all_node_data + FROM all_node_data2 ORDER BY era_id DESC LIMIT 1 "); @@ -44,39 +44,48 @@ public function getMetric(Request $request) $addresses = DB::select(" SELECT - a.public_key, - a.historical_performance AS uptime, - a.bid_delegators_count AS delegators, - a.port8888_peers AS peers, - a.bid_self_staked_amount, a.bid_total_staked_amount, - a.bad_mark, a.stable, a.in_auction - FROM all_node_data AS a + a.public_key, a.bid_delegators_count AS delegators, + a.bid_total_staked_amount, a.bid_self_staked_amount, + a.uptime, a.bid_inactive, a.in_current_era, a.in_auction + a.port8888_peers AS peers + FROM all_node_data2 AS a JOIN user_addresses AS b ON a.public_key = b.public_address_node - WHERE a.era_id = $current_era_id + JOIN users AS c + ON b.user_id = c.id + WHERE a.era_id = $current_era_id AND ( b.user_id = $user_id OR a.public_key = '$public_address_node' ) "); + + if (!$addresses) { + $addresses = array(); + } + $return_object['addresses'] = $addresses; foreach ($addresses as &$address) { $a = $address->public_key; - $eras_since_bad_mark = DB::select(" - SELECT a.era_id - FROM all_node_data AS a - JOIN user_addresses AS b - ON a.public_key = b.public_address_node - WHERE a.public_key = '$a' - AND a.bad_mark = 1 + $total_bad_marks = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$p' + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) ORDER BY era_id DESC - LIMIT 1 "); - $eras_since_bad_mark = $eras_since_bad_mark[0]->era_id ?? 0; - $eras_since_bad_mark = $current_era_id - $eras_since_bad_mark; + + $eras_since_bad_mark = $total_bad_marks[0] ?? array(); + $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); + $total_bad_marks = count((array)$total_bad_marks); + $address->eras_since_bad_mark = $eras_since_bad_mark; + $address->total_bad_marks = $total_bad_marks; } $monitoring_criteria = DB::select(" diff --git a/database/migrations/2022_10_20_172223_update_users_add_kyc_bypass.php b/database/migrations/2022_10_20_172223_update_users_add_kyc_bypass.php new file mode 100644 index 00000000..71dfb6d6 --- /dev/null +++ b/database/migrations/2022_10_20_172223_update_users_add_kyc_bypass.php @@ -0,0 +1,30 @@ +boolean('kyc_bypass_approval')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/routes/api.php b/routes/api.php index 78cfd42a..5a167ba8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -133,6 +133,9 @@ // New Eras page endpoint for specific selected user Route::get('/users/all-eras-user/{id}', [AdminController::class, 'allErasUser'])->where('id', '[0-9]+'); + // New admin endpoint for manual kyc bypass approval + Route::post('/users/bypass-approve-kyc/{user_id}', [AdminController::class, 'bypassApproveKYC'])->where('user_id', '[0-9]+'); + Route::get('/users', [AdminController::class, 'getUsers']); Route::get('/users/{id}', [AdminController::class, 'getUserDetail'])->where('id', '[0-9]+'); Route::get('/dashboard', [AdminController::class, 'infoDashboard']); From 947ef8edead2db45c24bbe1b981313d332f3d3a3 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 20 Oct 2022 13:46:59 -0400 Subject: [PATCH 049/162] # Metric Fix --- .../Controllers/Api/V1/MetricController.php | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/MetricController.php b/app/Http/Controllers/Api/V1/MetricController.php index 36d46616..ebb257b3 100644 --- a/app/Http/Controllers/Api/V1/MetricController.php +++ b/app/Http/Controllers/Api/V1/MetricController.php @@ -42,6 +42,7 @@ public function getMetric(Request $request) "); $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + /* $addresses = DB::select(" SELECT a.public_key, a.bid_delegators_count AS delegators, @@ -59,7 +60,26 @@ public function getMetric(Request $request) a.public_key = '$public_address_node' ) "); + */ + $addresses = DB::select(" + SELECT + a.public_key, a.bid_delegators_count AS delegators, + a.bid_total_staked_amount, a.bid_self_staked_amount, + a.uptime, a.bid_inactive, a.in_current_era, a.in_auction, + a.port8888_peers AS peers + FROM all_node_data2 AS a + JOIN user_addresses AS b + ON b.public_address_node = a.public_key + JOIN users AS c + ON c.id = b.user_id + WHERE a.era_id = $current_era_id + AND ( + b.user_id = $user_id OR + a.public_key = '$public_address_node' + ) + "); + if (!$addresses) { $addresses = array(); } @@ -72,7 +92,7 @@ public function getMetric(Request $request) $total_bad_marks = DB::select(" SELECT era_id FROM all_node_data2 - WHERE public_key = '$p' + WHERE public_key = '$a' AND ( in_current_era = 0 OR bid_inactive = 1 From b1b474095dab229dee4ac0d44c88d2948ec54754 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 20 Oct 2022 13:59:30 -0400 Subject: [PATCH 050/162] metrics stop using old table --- .../Controllers/Api/V1/MetricController.php | 494 ++---------------- 1 file changed, 32 insertions(+), 462 deletions(-) diff --git a/app/Http/Controllers/Api/V1/MetricController.php b/app/Http/Controllers/Api/V1/MetricController.php index ebb257b3..48815e91 100644 --- a/app/Http/Controllers/Api/V1/MetricController.php +++ b/app/Http/Controllers/Api/V1/MetricController.php @@ -42,26 +42,6 @@ public function getMetric(Request $request) "); $current_era_id = (int)($current_era_id[0]->era_id ?? 0); - /* - $addresses = DB::select(" - SELECT - a.public_key, a.bid_delegators_count AS delegators, - a.bid_total_staked_amount, a.bid_self_staked_amount, - a.uptime, a.bid_inactive, a.in_current_era, a.in_auction - a.port8888_peers AS peers - FROM all_node_data2 AS a - JOIN user_addresses AS b - ON a.public_key = b.public_address_node - JOIN users AS c - ON b.user_id = c.id - WHERE a.era_id = $current_era_id - AND ( - b.user_id = $user_id OR - a.public_key = '$public_address_node' - ) - "); - */ - $addresses = DB::select(" SELECT a.public_key, a.bid_delegators_count AS delegators, @@ -115,178 +95,6 @@ public function getMetric(Request $request) $return_object['monitoring_criteria'] = $monitoring_criteria; // info($return_object); return $this->successResponse($return_object); - //// done - - - - - - - - - - $isTotal = (int) $request->get('isTotal'); - - $max_update_responsiveness = DB::select(" - SELECT max(update_responsiveness) as max_update_responsiveness FROM - ( - SELECT MAX(update_responsiveness) as update_responsiveness FROM metric - UNION - SELECT MAX(update_responsiveness) as update_responsiveness FROM node_info - ) AS results;" - ); - $max_update_responsiveness = $max_update_responsiveness[0]->max_update_responsiveness ?? 0; - - $max_peers = DB::select(" - SELECT max(peers) as max_peers FROM ( - SELECT MAX(peers) as peers FROM metric - UNION - SELECT MAX(peers) as peers FROM node_info - ) AS results;" - ); - $max_peers = $max_peers[0]->max_peers ?? 0; - $max_block_height = Node::max('block_height'); - $max_uptime = DB::select(" - SELECT max(uptime) as max_uptime FROM ( - SELECT MAX(uptime) as uptime FROM metric - UNION - SELECT MAX(uptime) as uptime FROM node_info - ) AS results;" - ); - $max_uptime = $max_uptime[0]->max_uptime ?? 0; - $latest = Node::where('node_address', strtolower($public_address_node))->whereNotnull('protocol_version')->orderBy('created_at', 'desc')->first(); - - if (!$latest) { - $latest = new Node(); - } - - $latest_block_height = $latest->block_height ?? null; - $latest_update_responsiveness = $latest->update_responsiveness ?? null; - $latest_peers = $latest->peers ?? null; - - $metric = Metric::where('user_id', $user->id)->first(); - - if (!$metric) { - $metric = new Metric(); - } - - $metric_uptime = $metric->uptime ?? null; - $metric_block_height = $metric->block_height_average ? ($max_block_height - $metric->block_height_average) : null; - $metric_update_responsiveness = $metric->update_responsiveness ?? null; - $metric_peers = $metric->peers ?? null; - - $nodeInfo = NodeInfo::where('node_address', strtolower($public_address_node))->first(); - - if (!$nodeInfo) { - $nodeInfo = new NodeInfo(); - } - - $latest_uptime = $nodeInfo->uptime ?? null; - $nodeInfo_uptime = $nodeInfo->uptime ?? null; - $nodeInfo_block_height = $nodeInfo->block_height ?? null; - $nodeInfo_update_responsiveness = $nodeInfo->update_responsiveness ?? null; - $nodeInfo_peers = $nodeInfo->peers ?? null; - - $metric->avg_uptime = $nodeInfo_uptime ?? $metric_uptime; - $metric->avg_block_height_average = $nodeInfo_block_height ?? $metric_block_height; - $metric->avg_update_responsiveness = $nodeInfo_update_responsiveness ?? $metric_update_responsiveness; - $metric->avg_peers = $nodeInfo_peers ?? $metric_peers; - - $metric->max_peers = $max_peers; - $metric->max_update_responsiveness = $max_update_responsiveness; - $metric->max_block_height_average = $max_block_height; - $metric->max_uptime = $max_uptime; - - $metric->peers = $latest_peers ?? $metric_peers; - $metric->update_responsiveness = $latest_update_responsiveness ?? $metric_update_responsiveness; - $metric->block_height_average = $latest_block_height ?? $metric_block_height; - $metric->uptime = $latest_uptime ?? $metric_uptime; - - $monitoringCriteria = MonitoringCriteria::get(); - $nodeInfo = NodeInfo::where('node_address', strtolower($public_address_node))->first(); - - $userAddress = UserAddress::where('public_address_node', strtolower($public_address_node)) - ->where('user_id', $user->id) - ->first(); - - $rank = $user->rank; - - if ($userAddress) { - $rank = $userAddress->rank; - } - - $totalCount = UserAddress::select([ - 'users.id as user_id', - 'user_addresses.public_address_node', - 'user_addresses.is_fail_node', - 'user_addresses.rank' - ]) - ->join('users', 'users.id', '=', 'user_addresses.user_id') - ->where('users.banned', 0) - ->whereNotNull('user_addresses.public_address_node') - ->get() - ->count(); - - $delegators = $stake_amount = $self_stake_amount = 0; - if ($nodeInfo) { - $delegators = $nodeInfo->delegators_count; - $stake_amount = $nodeInfo->total_staked_amount; - $self_stake_amount = $nodeInfo->self_staked_amount; - } - - $mbs = NodeInfo::max('mbs'); - $metric->mbs = $mbs; - $metric->rank = $rank; - $metric->totalCount = $totalCount; - $metric->delegators = $delegators; - $metric->stake_amount = $stake_amount; - $metric->self_stake_amount = $self_stake_amount; - $metric['node_status'] = $user->node_status; - $metric['monitoring_criteria'] = $monitoringCriteria; - - $setting = Setting::where('name', 'peers')->first(); - - if ($setting) { - $metric['peers_setting'] = (int) $setting->value; - } else { - $metric['peers_setting'] = 0; - } - - $addresses = UserAddress::where('user_id', $user->id)->get(); - $metric['addresses'] = $addresses; - - if ($isTotal) { - $tempRank = $stake_amount = $self_stake_amount = $delegators = 0; - if ($addresses && count($addresses) > 0) { - $tempRank = UserAddress::get()->count(); - foreach ($addresses as $address) { - if ($tempRank > (int) $address->rank) { - $tempRank = (int) $address->rank; - } - } - } - - if ($tempRank > 0) { - $metric->rank = $tempRank; - } - - if ($addresses && count($addresses) > 0) { - foreach ($addresses as $address) { - $nodeInfo = NodeInfo::where('node_address', strtolower($address->public_address_node))->first(); - if ($nodeInfo) { - $stake_amount += $nodeInfo->total_staked_amount; - $delegators += $nodeInfo->delegators_count; - $self_stake_amount += $nodeInfo->self_staked_amount; - } - } - } - $metric->delegators = $delegators; - $metric->stake_amount = $stake_amount; - $metric->self_stake_amount = $self_stake_amount; - } - // info($metric); - - return $this->successResponse($metric); } public function updateMetric(Request $request, $id) @@ -359,7 +167,7 @@ public function getMetricUser($id) $current_era_id = DB::select(" SELECT era_id - FROM all_node_data + FROM all_node_data2 ORDER BY era_id DESC LIMIT 1 "); @@ -367,63 +175,37 @@ public function getMetricUser($id) $addresses = DB::select(" SELECT - a.public_key, - a.historical_performance AS uptime, - a.bid_delegators_count AS delegators, + a.public_key, a.uptime, a.port8888_peers AS peers, - a.bid_self_staked_amount, a.bid_total_staked_amount, - a.bad_mark, a.stable, a.in_auction - FROM all_node_data AS a + a.bid_inactive, a.in_current_era + FROM all_node_data2 AS a JOIN user_addresses AS b ON a.public_key = b.public_address_node - WHERE a.era_id = $current_era_id - AND b.user_id = $user_id + WHERE a.era_id = $current_era_id + AND b.user_id = $user_id "); $return_object['addresses'] = $addresses; foreach ($addresses as &$address) { - $a = $address->public_key; - - $eras_since_bad_mark = DB::select(" - SELECT a.era_id - FROM all_node_data AS a - JOIN user_addresses AS b - ON a.public_key = b.public_address_node - WHERE a.public_key = '$a' - AND a.bad_mark = 1 - ORDER BY era_id DESC - LIMIT 1 - "); - $eras_since_bad_mark = $eras_since_bad_mark[0]->era_id ?? 0; - $eras_since_bad_mark = $current_era_id - $eras_since_bad_mark; - $address->eras_since_bad_mark = $eras_since_bad_mark; - - $eras_active = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$a' - ORDER BY era_id ASC - LIMIT 1 - "); - $eras_active = (int) ($eras_active[0]->era_id ?? 0); - if ($current_era_id - $eras_active > 0) { - $address->eras_active = $current_era_id - $eras_active; - } else { - $address->eras_active = 0; - } + $p = $address->public_key; $total_bad_marks = DB::select(" SELECT era_id FROM all_node_data2 - WHERE public_key = '$a' + WHERE public_key = '$p' AND ( in_current_era = 0 OR bid_inactive = 1 ) ORDER BY era_id DESC "); - $address->total_bad_marks = count((array)$total_bad_marks); + $eras_since_bad_mark = $total_bad_marks[0] ?? array(); + $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); + $total_bad_marks = count((array)$total_bad_marks); + + $address->eras_since_bad_mark = $eras_since_bad_mark; + $address->total_bad_marks = $total_bad_marks; $address->update_responsiveness = 100; } @@ -434,115 +216,6 @@ public function getMetricUser($id) $return_object['monitoring_criteria'] = $monitoring_criteria; // info($return_object); return $this->successResponse($return_object); - //// done - - - - - - $max_update_responsiveness = DB::select("SELECT max(update_responsiveness) as max_update_responsiveness FROM - ( - SELECT MAX(update_responsiveness) as update_responsiveness FROM metric - UNION - SELECT MAX(update_responsiveness) as update_responsiveness FROM node_info - ) AS results; - "); - $max_update_responsiveness = $max_update_responsiveness[0]->max_update_responsiveness ?? 0; - - $max_peers = DB::select(" - SELECT max(peers) as max_peers FROM - ( - SELECT MAX(peers) as peers FROM metric - UNION - SELECT MAX(peers) as peers FROM node_info - ) AS results; - "); - $max_peers = $max_peers[0]->max_peers ?? 0; - $max_block_height = Node::max('block_height'); - $max_uptime = DB::select(" - SELECT max(uptime) as max_uptime FROM - ( - SELECT MAX(uptime) as uptime FROM metric - UNION - SELECT MAX(uptime) as uptime FROM node_info - ) AS results; - "); - $max_uptime = $max_uptime[0]->max_uptime ?? 0; - $latest = Node::where('node_address', strtolower($user->public_address_node)) - ->whereNotnull('protocol_version') - ->orderBy('created_at', 'desc') - ->first(); - - if (!$latest) $latest = new Node(); - $latest_block_height = $latest->block_height ?? null; - $latest_update_responsiveness = $latest->update_responsiveness ?? null; - $latest_peers = $latest->peers ?? null; - - $metric = Metric::where('user_id', $user->id)->first(); - if (!$metric) $metric = new Metric(); - - $metric_uptime = $metric->uptime ?? null; - $metric_block_height = $metric->block_height_average ? ($max_block_height - $metric->block_height_average) : null; - $metric_update_responsiveness = $metric->update_responsiveness ?? null; - $metric_peers = $metric->peers ?? null; - - $nodeInfo = NodeInfo::where('node_address', strtolower($user->public_address_node))->first(); - if (!$nodeInfo) $nodeInfo = new NodeInfo(); - - $latest_uptime = $nodeInfo->uptime ?? null; - $nodeInfo_uptime = $nodeInfo->uptime ?? null; - $nodeInfo_block_height = $nodeInfo->block_height ?? null; - $nodeInfo_update_responsiveness = $nodeInfo->update_responsiveness ?? null; - $nodeInfo_peers = $nodeInfo->peers ?? null; - - $metric->avg_uptime = $nodeInfo_uptime ?? $metric_uptime; - $metric->avg_block_height_average = $nodeInfo_block_height ?? $metric_block_height; - $metric->avg_update_responsiveness = $nodeInfo_update_responsiveness ?? $metric_update_responsiveness; - $metric->avg_peers = $nodeInfo_peers ?? $metric_peers; - - $metric->max_peers = $max_peers; - $metric->max_update_responsiveness = $max_update_responsiveness; - $metric->max_block_height_average = $max_block_height; - $metric->max_uptime = $max_uptime; - - $metric->peers = $latest_peers ?? $metric_peers; - $metric->update_responsiveness = $latest_update_responsiveness ?? $metric_update_responsiveness; - $metric->block_height_average = $latest_block_height ?? $metric_block_height; - $metric->uptime = $latest_uptime ?? $metric_uptime; - - $monitoringCriteria = MonitoringCriteria::get(); - $nodeInfo = NodeInfo::where('node_address', strtolower($user->public_address_node))->first(); - - $rank = $user->rank; - - $totalCount = User::select([ - 'id as user_id', - 'public_address_node', - 'is_fail_node', - 'rank', - ]) - ->where('banned', 0) - ->whereNotNull('public_address_node') - ->get() - ->count(); - - $delegators = $stake_amount = $self_stake_amount = 0; - if ($nodeInfo) { - $delegators = $nodeInfo->delegators_count; - $stake_amount = $nodeInfo->total_staked_amount; - $self_stake_amount = $nodeInfo->self_staked_amount; - } - $mbs = NodeInfo::max('mbs'); - $metric->mbs = $mbs; - $metric->rank = $rank; - $metric->totalCount = $totalCount; - $metric->delegators = $delegators; - $metric->stake_amount = $stake_amount; - $metric->self_stake_amount = $self_stake_amount; - $metric['node_status'] = $user->node_status; - $metric['monitoring_criteria'] = $monitoringCriteria; - - return $this->successResponse($metric); } public function getMetricUserByNodeName($node) @@ -552,7 +225,7 @@ public function getMetricUserByNodeName($node) $current_era_id = DB::select(" SELECT era_id - FROM all_node_data + FROM all_node_data2 ORDER BY era_id DESC LIMIT 1 "); @@ -560,38 +233,39 @@ public function getMetricUserByNodeName($node) $addresses = DB::select(" SELECT - a.public_key, - a.historical_performance AS uptime, + a.public_key, a.uptime, a.bid_delegators_count AS delegators, a.port8888_peers AS peers, + a.bid_inactive, a.in_current_era, a.bid_self_staked_amount, a.bid_total_staked_amount, - a.bad_mark, a.stable, c.node_status - FROM all_node_data AS a + FROM all_node_data2 AS a JOIN user_addresses AS b ON a.public_key = b.public_address_node JOIN users AS c ON b.user_id = c.id - WHERE a.era_id = $current_era_id + WHERE a.era_id = $current_era_id AND b.public_address_node = '$node' "); $return_object['addresses'] = $addresses; foreach ($addresses as &$address) { - $a = $address->public_key; + $p = $address->public_key; - $eras_since_bad_mark = DB::select(" - SELECT a.era_id - FROM all_node_data AS a - JOIN user_addresses AS b - ON a.public_key = b.public_address_node - WHERE a.public_key = '$a' - AND a.bad_mark = 1 + $total_bad_marks = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$p' + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) ORDER BY era_id DESC - LIMIT 1 "); - $eras_since_bad_mark = $eras_since_bad_mark[0]->era_id ?? 0; - $eras_since_bad_mark = $current_era_id - $eras_since_bad_mark; + + $eras_since_bad_mark = $total_bad_marks[0] ?? array(); + $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); + $address->eras_since_bad_mark = $eras_since_bad_mark; } @@ -602,109 +276,5 @@ public function getMetricUserByNodeName($node) $return_object['monitoring_criteria'] = $monitoring_criteria; // info($return_object); return $this->successResponse($return_object); - //// done - - - - - - $userAddress = UserAddress::with('user') - ->has('user') - ->where('public_address_node', $node) - ->first(); - - if ($userAddress && isset($userAddress->user) && $userAddress->user) { - $user = $userAddress->user; - $public_address_node = strtolower($userAddress->public_address_node); - - $max_update_responsiveness = DB::select(" - SELECT max(update_responsiveness) as max_update_responsiveness FROM - ( - SELECT MAX(update_responsiveness) as update_responsiveness FROM metric - UNION - SELECT MAX(update_responsiveness) as update_responsiveness FROM node_info - ) AS results;" - ); - $max_update_responsiveness = $max_update_responsiveness[0]->max_update_responsiveness ?? 0; - - $max_peers = DB::select(" - SELECT max(peers) as max_peers FROM - ( - SELECT MAX(peers) as peers FROM metric - UNION - SELECT MAX(peers) as peers FROM node_info - ) AS results;" - ); - $max_peers = $max_peers[0]->max_peers ?? 0; - $max_block_height = Node::max('block_height'); - $max_uptime = DB::select(" - SELECT max(uptime) as max_uptime FROM - ( - SELECT MAX(uptime) as uptime FROM metric - UNION - SELECT MAX(uptime) as uptime FROM node_info - ) AS results;" - ); - $max_uptime = $max_uptime[0]->max_uptime ?? 0; - - $latest = Node::where('node_address', strtolower($public_address_node))->whereNotnull('protocol_version')->orderBy('created_at', 'desc')->first(); - if (!$latest) $latest = new Node(); - - $latest_block_height = $latest->block_height ?? null; - $latest_update_responsiveness = $latest->update_responsiveness ?? null; - $latest_peers = $latest->peers ?? null; - - $metric = Metric::where('user_id', $user->id)->first(); - if (!$metric) $metric = new Metric(); - $metric_uptime = $metric->uptime ?? null; - $metric_block_height = $metric->block_height_average ? ($max_block_height - $metric->block_height_average) : null; - $metric_update_responsiveness = $metric->update_responsiveness ?? null; - $metric_peers = $metric->peers ?? null; - - $nodeInfo = NodeInfo::where('node_address', strtolower($public_address_node))->first(); - if (!$nodeInfo) $nodeInfo = new NodeInfo(); - - $latest_uptime = $nodeInfo->uptime ?? null; - $nodeInfo_uptime = $nodeInfo->uptime ?? null; - $nodeInfo_block_height = $nodeInfo->block_height ?? null; - $nodeInfo_peers = $nodeInfo->peers ?? null; - $nodeInfo_update_responsiveness = $nodeInfo->update_responsiveness ?? null; - - $metric->avg_uptime = $nodeInfo_uptime ?? $metric_uptime; - $metric->avg_block_height_average = $nodeInfo_block_height ?? $metric_block_height; - $metric->avg_update_responsiveness = $nodeInfo_update_responsiveness ?? $metric_update_responsiveness; - $metric->avg_peers = $nodeInfo_peers ?? $metric_peers; - - $metric->max_peers = $max_peers; - $metric->max_update_responsiveness = $max_update_responsiveness; - $metric->max_block_height_average = $max_block_height; - $metric->max_uptime = $max_uptime; - - $metric->peers = $latest_peers ?? $metric_peers; - $metric->update_responsiveness = $latest_update_responsiveness ?? $metric_update_responsiveness; - $metric->block_height_average = $latest_block_height ?? $metric_block_height; - $metric->uptime = $latest_uptime ?? $metric_uptime; - - $monitoringCriteria = MonitoringCriteria::get(); - $nodeInfo = NodeInfo::where('node_address', strtolower($public_address_node))->first(); - $rank = $userAddress->rank; - $delegators = $stake_amount = $self_stake_amount = $is_open_port = 0; - if ($nodeInfo) { - $delegators = $nodeInfo->delegators_count; - $stake_amount = $nodeInfo->total_staked_amount; - $self_stake_amount = $nodeInfo->self_staked_amount; - $is_open_port = $nodeInfo->is_open_port; - } - $mbs = NodeInfo::max('mbs'); - $metric->mbs = $mbs; - $metric->rank = $rank; - $metric->is_open_port = $is_open_port; - $metric->delegators = $delegators; - $metric->self_stake_amount = $self_stake_amount; - $metric['node_status'] = $user->node_status; - $metric['monitoring_criteria'] = $monitoringCriteria; - return $this->successResponse($metric); - } - return $this->successResponse([]); } } \ No newline at end of file From 5f61171cf49f903b4186409c1c66089fca7a96ae Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 20 Oct 2022 14:41:50 -0400 Subject: [PATCH 051/162] # Metric Update --- app/Http/Controllers/Api/V1/MetricController.php | 11 ++++++++++- app/Http/Controllers/Api/V1/UserController.php | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/V1/MetricController.php b/app/Http/Controllers/Api/V1/MetricController.php index 48815e91..764c89cd 100644 --- a/app/Http/Controllers/Api/V1/MetricController.php +++ b/app/Http/Controllers/Api/V1/MetricController.php @@ -93,7 +93,16 @@ public function getMetric(Request $request) FROM monitoring_criteria "); $return_object['monitoring_criteria'] = $monitoring_criteria; - // info($return_object); + + $items = Setting::get(); + $settings = []; + if ($items) { + foreach ($items as $item) { + $settings[$item->name] = $item->value; + } + } + $return_object['globalSettings'] = $settings; + return $this->successResponse($return_object); } diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 8143b180..f650c506 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -534,8 +534,8 @@ public function getMembershipPage() { $addresses_count = count($addresses); $addresses_count = $addresses_count ? $addresses_count : 1; - $return["avg_uptime"] = $return["avg_uptime"] / $addresses_count; - + $return["avg_uptime"] = round((float) ($return["avg_uptime"] / $addresses_count), 2); + // info($return); return $this->successResponse($return); } From 2be5d0a3caccf6c54ed48a822f0fc58a6c9003bc Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 20 Oct 2022 14:44:07 -0400 Subject: [PATCH 052/162] # Metric Update --- app/Http/Controllers/Api/V1/MetricController.php | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/app/Http/Controllers/Api/V1/MetricController.php b/app/Http/Controllers/Api/V1/MetricController.php index 764c89cd..bc9c0d14 100644 --- a/app/Http/Controllers/Api/V1/MetricController.php +++ b/app/Http/Controllers/Api/V1/MetricController.php @@ -28,12 +28,7 @@ public function getMetric(Request $request) $return_object = array(); $user = auth()->user()->load(['pagePermissions']); $user_id = $user->id; - $public_address_node = $request->get('public_address_node'); - - if (!$public_address_node) { - $public_address_node = $user->public_address_node; - } - + $current_era_id = DB::select(" SELECT era_id FROM all_node_data2 @@ -54,10 +49,7 @@ public function getMetric(Request $request) JOIN users AS c ON c.id = b.user_id WHERE a.era_id = $current_era_id - AND ( - b.user_id = $user_id OR - a.public_key = '$public_address_node' - ) + AND b.user_id = $user_id "); if (!$addresses) { @@ -102,7 +94,7 @@ public function getMetric(Request $request) } } $return_object['globalSettings'] = $settings; - + return $this->successResponse($return_object); } From 7181c20237b87e10eaada007e0f8c9df0748c0c4 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 20 Oct 2022 14:49:00 -0400 Subject: [PATCH 053/162] # User Profile Update --- app/Http/Controllers/Api/V1/UserController.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index f650c506..8b6cb016 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -40,6 +40,7 @@ use App\Models\VerifyUser; use App\Models\Vote; use App\Models\VoteResult; +use App\Models\Setting; use App\Repositories\OwnerNodeRepository; use App\Repositories\ProfileRepository; @@ -535,7 +536,7 @@ public function getMembershipPage() { $addresses_count = count($addresses); $addresses_count = $addresses_count ? $addresses_count : 1; $return["avg_uptime"] = round((float) ($return["avg_uptime"] / $addresses_count), 2); - + // info($return); return $this->successResponse($return); } @@ -1177,6 +1178,16 @@ public function getProfile() $user = auth()->user()->load(['profile', 'pagePermissions', 'permissions', 'shuftipro', 'shuftiproTemp']); Helper::getAccountInfoStandard($user); $user->metric = Helper::getNodeInfo($user); + + $items = Setting::get(); + $settings = []; + if ($items) { + foreach ($items as $item) { + $settings[$item->name] = $item->value; + } + } + $user->globalSettings = $settings; + return $this->successResponse($user); } From 8fff0a4337017a5f6dd92ea059825f74380bfd86 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 20 Oct 2022 16:38:44 -0400 Subject: [PATCH 054/162] # Bypass KYC --- .../Controllers/Api/V1/AdminController.php | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index b24312d4..277ba5c9 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -958,26 +958,37 @@ public function getKYC($id) public function bypassApproveKYC($user_id) { - $user_id = (int)$user_id; + $user_id = (int) $user_id; $now = Carbon::now('UTC'); - DB::table('users') - ->where('id', $user_id) - ->update( - array( - 'kyc_verified_at' => $now, - 'approve_at' => $now, - 'kyc_bypass_approval' => 1 - ) - ); + $user = User::find($user_id); + if ($user && $user->role == 'member') { + $user->kyc_verified_at = $now; + $user->approve_at = $now; + $user->kyc_bypass_approval = 1; + $user->save(); - DB::table('profile') - ->where('user_id', $user_id) - ->update( - array( - 'status' => 'approved' - ) - ); + $profile = Profile::where('user_id', $user_id)->first(); + if (!$profile) { + $profile = new Profile; + $profile->user_id = $user_id; + $profile->first_name = $user->first_name; + $profile->last_name = $user->last_name; + $profile->type = $user->type; + } + $profile->status = 'approved'; + $profile->save(); + + $shuftipro = Shuftipro::where('user_id', $user_id)->first(); + if (!$shuftipro) { + $shuftipro = new Shuftipro; + $shuftipro->user_id = $user_id; + $shuftipro->reference_id = 'ByPass#' . time(); + } + $shuftipro->is_successful = 1; + $shuftipro->status = 'approved'; + $shuftipro->save(); + } return $this->metaSuccess(); } From 4b9feea62767aac43c19d1aebb6d5db8487e36b9 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Fri, 21 Oct 2022 12:07:04 -0400 Subject: [PATCH 055/162] # Member Data Updates --- app/Console/Helper.php | 14 +++ .../Controllers/Api/V1/MetricController.php | 1 - .../Controllers/Api/V1/UserController.php | 114 ++++++++++-------- app/Models/AllNodeData2.php | 12 ++ app/Services/NodeHelper.php | 1 - 5 files changed, 90 insertions(+), 52 deletions(-) create mode 100644 app/Models/AllNodeData2.php diff --git a/app/Console/Helper.php b/app/Console/Helper.php index 0a87d558..8a3b22cb 100644 --- a/app/Console/Helper.php +++ b/app/Console/Helper.php @@ -22,6 +22,20 @@ class Helper { + public static function getCurrentERAId() { + $record = DB::select(" + SELECT era_id + FROM all_node_data2 + ORDER BY era_id DESC + LIMIT 1 + "); + if ($record && count($record) > 0) { + $current_era_id = (int) ($record[0]->era_id ?? 0); + return $current_era_id; + } + return 0; + } + public static function isAccessBlocked($user, $page) { if ($user->role == 'admin') return false; $flag = false; diff --git a/app/Http/Controllers/Api/V1/MetricController.php b/app/Http/Controllers/Api/V1/MetricController.php index bc9c0d14..bace0061 100644 --- a/app/Http/Controllers/Api/V1/MetricController.php +++ b/app/Http/Controllers/Api/V1/MetricController.php @@ -215,7 +215,6 @@ public function getMetricUser($id) FROM monitoring_criteria "); $return_object['monitoring_criteria'] = $monitoring_criteria; - // info($return_object); return $this->successResponse($return_object); } diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 8b6cb016..4e395d9a 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -41,6 +41,7 @@ use App\Models\Vote; use App\Models\VoteResult; use App\Models\Setting; +use App\Models\AllNodeData2; use App\Repositories\OwnerNodeRepository; use App\Repositories\ProfileRepository; @@ -2596,47 +2597,8 @@ public function getMembers(Request $request) return $this->successResponse($users); } - public function getMemberDetail($id, Request $request) - { - $public_address_node = $request->get('public_address_node') ?? null; - - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); - - $response = DB::select(" - SELECT - a.public_address_node, a.node_verified_at, - b.email, b.first_name, b.last_name, b.created_at, - b.kyc_verified_at, b.entity_name, - c.uptime, c.bid_delegators_count, - c.bid_delegation_rate, c.bid_self_staked_amount, c.bid_total_staked_amount, - d.casper_association_kyc_hash, d.blockchain_name, d.blockchain_desc - FROM user_addresses AS a - JOIN users AS b - ON a.user_id = b.id - JOIN all_node_data2 AS c - ON a.public_address_node = c.public_key - JOIN profile AS d - ON b.id = d.user_id - WHERE ( - b.id = $id AND - c.era_id = $current_era_id - ) OR ( - c.public_key = '$public_address_node' AND - c.era_id = $current_era_id - ) - "); - // info($response); - // return $this->successResponse($response); - //// done - + public function getMemberDetail($id, Request $request) { $user = User::where('id', $id)->first(); - if (!$user || $user->role == 'admin') { return $this->errorResponse( __('api.error.not_found'), @@ -2646,18 +2608,70 @@ public function getMemberDetail($id, Request $request) Helper::getAccountInfoStandard($user); - $user->metric = Helper::getNodeInfo($user, $public_address_node); - $response = $user->load(['profile', 'addresses']); + $current_era_id = Helper::getCurrentERAId(); + + $response = [ + 'id' => $user->id, + 'full_name' => $user->full_name, + 'avatar_url' => $user->avatar_url, + 'first_name' => $user->first_name, + 'last_name' => $user->last_name, + 'email' => $user->email, + 'pseudonym' => $user->pseudonym, + 'telegram' => $user->telegram, + 'type' => $user->type, + 'entity_name' => $user->entity_name, + 'entity_type' => $user->entity_type, + 'entity_register_number' => $user->entity_register_number, + 'entity_register_country' => $user->entity_register_country, + 'entity_tax' => $user->entity_tax, + 'public_address_node' => $user->public_address_node, + 'node_verified_at' => $user->node_verified_at, + 'member_status' => $user->member_status, + 'message_content' => $user->message_content, + 'email_verified_at' => $user->email_verified_at, + 'kyc_verified_at' => $user->kyc_verified_at, + 'letter_verified_at' => $user->letter_verified_at, + 'letter_rejected_at' => $user->letter_rejected_at, + 'approve_at' => $user->approve_at, + 'role' => $user->role, + 'node_status' => $user->node_status + ]; - unset($response->last_login_at); - unset($response->last_login_ip_address); - unset($response->email_verified_at); + if ($user->profile) { + $response['profile'] = [ + 'id' => $user->profile->id, + 'status' => $user->profile->status, + 'extra_status' => $user->profile->extra_status, + 'casper_association_kyc_hash' => $user->profile->casper_association_kyc_hash, + 'blockchain_name' => $user->profile->blockchain_name, + 'blockchain_desc' => $user->profile->blockchain_desc, + 'type' => $user->profile->type + ]; + } - if (isset($response->profile)) { - unset($response->profile->dob); - unset($response->profile->address); - unset($response->profile->city); - unset($response->profile->zip); + $response['addresses'] = $user->addresses ?? []; + + foreach ($response['addresses'] as &$addressItem) { + $temp = AllNodeData2::select([ + 'uptime', + 'bid_delegators_count', + 'bid_delegation_rate', + 'bid_self_staked_amount', + 'bid_total_staked_amount' + ]) + ->where('public_key', $addressItem->public_address_node) + ->where('era_id', $current_era_id) + ->orderBy('id', 'desc') + ->first() + ->toArray(); + if ($temp) { + foreach ($temp as $key => $value) { + if ($key == 'uptime') $value = round((float) $value, 2); + $addressItem->$key = $value; + } + $addressItem->update_responsiveness = 100; + } } return $this->successResponse($response); diff --git a/app/Models/AllNodeData2.php b/app/Models/AllNodeData2.php new file mode 100644 index 00000000..af34815c --- /dev/null +++ b/app/Models/AllNodeData2.php @@ -0,0 +1,12 @@ + Date: Fri, 21 Oct 2022 12:13:45 -0400 Subject: [PATCH 056/162] # API Update --- app/Http/Controllers/Api/V1/UserController.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 4e395d9a..54d9c63f 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -2648,6 +2648,10 @@ public function getMemberDetail($id, Request $request) { 'blockchain_desc' => $user->profile->blockchain_desc, 'type' => $user->profile->type ]; + } else { + $response['profile'] = [ + 'type' => $user->type + ]; } $response['addresses'] = $user->addresses ?? []; From d3ea4de901efa4551f021fec681f7df3c428a24a Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Fri, 21 Oct 2022 12:45:14 -0400 Subject: [PATCH 057/162] # Get List Nodes By --- .../Controllers/Api/V1/UserController.php | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 54d9c63f..38f153de 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -3027,7 +3027,32 @@ function ($object) { public function getListNodesBy(Request $request) { $user = auth()->user(); - $addresses = UserAddress::where('user_id', $user->id)->orderBy('id', 'asc')->get(); + $addresses = $user->addresses ?? []; + + $current_era_id = Helper::getCurrentERAId(); + + foreach ($addresses as &$addressItem) { + $temp = AllNodeData2::select([ + 'uptime', + 'bid_delegators_count', + 'bid_delegation_rate', + 'bid_self_staked_amount', + 'bid_total_staked_amount' + ]) + ->where('public_key', $addressItem->public_address_node) + ->where('era_id', $current_era_id) + ->orderBy('id', 'desc') + ->first() + ->toArray(); + if ($temp) { + foreach ($temp as $key => $value) { + if ($key == 'uptime') $value = round((float) $value, 2); + $addressItem->$key = $value; + } + $addressItem->update_responsiveness = 100; + } + } + return $this->successResponse([ 'addresses' => $addresses, ]); From ab5b1d0709cdf821f0692e52d19d60acea56e55f Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Fri, 21 Oct 2022 13:24:17 -0400 Subject: [PATCH 058/162] # Get User Detail --- .../Controllers/Api/V1/AdminController.php | 73 +++++++++++++++---- 1 file changed, 59 insertions(+), 14 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 277ba5c9..2ed8e03f 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -42,6 +42,7 @@ use App\Models\Vote; use App\Models\VoteResult; use App\Models\ContactRecipient; +use App\Models\AllNodeData2; use App\Services\NodeHelper; @@ -709,31 +710,75 @@ public function getUserDetail($id) $user = $user->load(['pagePermissions', 'profile', 'shuftipro', 'shuftiproTemp']); $status = 'Not Verified'; - if ($user->profile && $user->profile->status == 'approved') { $status = 'Verified'; - if ($user->profile->extra_status) { $status = $user->profile->extra_status; } } - $user = $user->load(['profile', 'shuftipro', 'shuftiproTemp']); - $status = 'Not Verified'; - - if ( - $user->profile && - $user->profile->status == 'approved' - ) { - $status = 'Verified'; + $user->membership_status = $status; + $user->metric = Helper::getNodeInfo($user); - if ($user->profile->extra_status) { - $status = $user->profile->extra_status; + $addresses = $user->addresses ?? []; + $current_era_id = Helper::getCurrentERAId(); + + foreach ($addresses as &$addressItem) { + $temp = AllNodeData2::select([ + 'uptime', + 'bid_delegators_count', + 'bid_delegation_rate', + 'bid_self_staked_amount', + 'bid_total_staked_amount' + ]) + ->where('public_key', $addressItem->public_address_node) + ->where('era_id', $current_era_id) + ->orderBy('id', 'desc') + ->first() + ->toArray(); + if ($temp) { + foreach ($temp as $key => $value) { + if ($key == 'uptime') $value = round((float) $value, 2); + $addressItem->$key = $value; + } + $addressItem->update_responsiveness = 100; + $p = $addressItem->public_address_node; + + $total_bad_marks = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$p' + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) + ORDER BY era_id DESC + "); + + $eras_active = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$p' + ORDER BY era_id ASC + LIMIT 1 + "); + $eras_active = (int)($eras_active[0]->era_id ?? 0); + + $eras_since_bad_mark = $total_bad_marks[0] ?? array(); + $eras_since_bad_mark = $current_era_id - (int) ($eras_since_bad_mark->era_id ?? 0); + $total_bad_marks = count((array)$total_bad_marks); + + $addressItem->eras_since_bad_mark = $eras_since_bad_mark; + $addressItem->total_bad_marks = $total_bad_marks; + $addressItem->eras_active = 0; + if ($current_era_id > $eras_active) { + $addressItem->eras_active = $current_era_id - $eras_active; + } } } - $user->membership_status = $status; - $user->metric = Helper::getNodeInfo($user); + $user->addresses = $addresses; + return $this->successResponse($user); } From 3680ae36898db164933573d1c8a5a70d39cf2878 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Sun, 23 Oct 2022 10:07:12 -0400 Subject: [PATCH 059/162] # Updates --- app/Http/Controllers/Api/V1/UserController.php | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 38f153de..0a65374e 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -2332,13 +2332,7 @@ public function uploadAvatar(Request $request) public function getMembers(Request $request) { - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + $current_era_id = Helper::getCurrentERAId(); $members = DB::table('users') ->select( @@ -2416,12 +2410,8 @@ public function getMembers(Request $request) ); } - // info($members); return $this->successResponse($members); - //// done - - - + $search = $request->search; $limit = $request->limit ?? 50; @@ -2430,7 +2420,7 @@ public function getMembers(Request $request) $slide_value_delegotors = $request->delegators ?? 0; $slide_value_stake_amount = $request->stake_amount ?? 0; $slide_delegation_rate = $request->delegation_rate ?? 0; - + $max_uptime = Node::max('uptime'); $max_uptime = $max_uptime * 100; $max_delegators = NodeInfo::max('delegators_count'); From ccaff0d479a817f1171322438a92a6e76b4dae2b Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Sun, 23 Oct 2022 12:10:06 -0400 Subject: [PATCH 060/162] # Updates --- app/Http/Controllers/Api/V1/UserController.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 0a65374e..12bbc2b0 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -2333,6 +2333,7 @@ public function uploadAvatar(Request $request) public function getMembers(Request $request) { $current_era_id = Helper::getCurrentERAId(); + $search = $request->search; $members = DB::table('users') ->select( @@ -2362,6 +2363,13 @@ public function getMembers(Request $request) 'users.banned' => 0, 'all_node_data2.era_id' => $current_era_id ]) + ->where(function ($query) use ($search) { + if ($search) { + $query->where('users.first_name', 'like', '%' . $search . '%') + ->orWhere('users.last_name', 'like', '%' . $search . '%') + ->orWhere('users.pseudonym', 'like', '%' . $search . '%'); + } + }) ->get(); $max_delegators = 0; @@ -2412,7 +2420,6 @@ public function getMembers(Request $request) return $this->successResponse($members); - $search = $request->search; $limit = $request->limit ?? 50; $slide_value_uptime = $request->uptime ?? 0; From 8884bbb68c550753b682cdf08fe27f437f4ded50 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Sun, 23 Oct 2022 12:39:43 -0400 Subject: [PATCH 061/162] # Admin Controller --- app/Http/Controllers/Api/V1/AdminController.php | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 2ed8e03f..a6cdd8ea 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -784,13 +784,7 @@ public function getUserDetail($id) public function infoDashboard(Request $request) { - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + $current_era_id = Helper::getCurrentERAId(); // define return object $return = array( @@ -862,7 +856,7 @@ public function infoDashboard(Request $request) // get avg responsiveness $avg_responsiveness = 100; - + // get max peers $max_peers = DB::select(" SELECT MAX(a.port8888_peers) AS max_peers From 7540499dd1527a6be9a15f5bccf5b9929c4a2f22 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 24 Oct 2022 11:22:17 -0400 Subject: [PATCH 062/162] clean up functions, historical_performance bug in admin get nodes page --- .../Controllers/Api/V1/AdminController.php | 45 +------------------ .../Controllers/Api/V1/UserController.php | 20 --------- 2 files changed, 1 insertion(+), 64 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index a6cdd8ea..724f43af 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -406,7 +406,7 @@ public function getNodesPage() { $ranking = DB::select(" SELECT public_key, - historical_performance AS uptime, + uptime, bid_delegators_count, bid_delegation_rate, bid_total_staked_amount @@ -651,49 +651,6 @@ public function getUsers(Request $request) } // info($users); return $this->successResponse($users); - //// done - - $limit = $request->limit ?? 50; - $sort_key = $request->sort_key ?? 'created_at'; - $sort_direction = $request->sort_direction ?? 'desc'; - - $users = User::where('role', 'member') - ->with(['profile']) - ->leftJoin('node_info', 'users.public_address_node', '=', 'node_info.node_address') - ->select([ - 'users.*', - 'node_info.delegation_rate', - 'node_info.delegators_count', - 'node_info.self_staked_amount', - 'node_info.total_staked_amount', - ]) - ->get(); - - foreach ($users as $user) { - $status = 'Not Verified'; - - if ($user->profile && $user->profile->status == 'approved') { - $status = 'Verified'; - - if ($user->profile->extra_status) { - $status = $user->profile->extra_status; - } - } - - $user->membership_status = $status; - } - - if ($sort_direction == 'desc') { - $users = $users->sortByDesc($sort_key)->values(); - } else { - $users = $users->sortBy($sort_key)->values(); - } - - $users = Helper::paginate($users, $limit, $request->page); - $users = $users->toArray(); - $users['data'] = (collect($users['data'])->values()); - - return $this->successResponse($users); } public function getUserDetail($id) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 12bbc2b0..528f1397 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1050,26 +1050,6 @@ public function getVerifiedMembers(Request $request) { "); // info($members); return $this->successResponse($members); - //// done - - - - - $data = []; - $limit = $request->limit ?? 50; - $data = User::select([ - 'users.id', - 'users.pseudonym', - 'users.public_address_node', - 'users.node_status', - 'profile.extra_status', - ]) - ->join('profile', 'profile.user_id', '=', 'users.id') - ->where('profile.status', 'approved') - ->whereNotNull('users.public_address_node') - ->paginate($limit); - // info($data); - return $this->successResponse($data); } // Shuftipro Webhook From 1392e60215049e503a17ec54da2defb79b480597 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 24 Oct 2022 11:29:35 -0400 Subject: [PATCH 063/162] Ranking query memory issue --- app/Http/Controllers/Api/V1/AdminController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 724f43af..72661358 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -411,7 +411,8 @@ public function getNodesPage() { bid_delegation_rate, bid_total_staked_amount FROM all_node_data2 - WHERE in_current_era = 1 + WHERE era_id = $current_era_id + AND in_current_era = 1 AND in_next_era = 1 AND in_auction = 1 "); From a598b8a7bcd3fb441e32f6a63948d854de937a21 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 24 Oct 2022 11:32:50 -0400 Subject: [PATCH 064/162] bug fix --- app/Http/Controllers/Api/V1/AdminController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 72661358..e1845975 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -508,7 +508,7 @@ function($x, $y) { $total_bad_marks = DB::select(" SELECT era_id FROM all_node_data2 - WHERE public_key = '$p' + WHERE public_key = '$a' AND ( in_current_era = 0 OR bid_inactive = 1 @@ -523,7 +523,7 @@ function($x, $y) { $total_eras = DB::select(" SELECT era_id FROM all_node_data2 - WHERE public_key = '$p' + WHERE public_key = '$a' ORDER BY era_id ASC LIMIT 1 "); From a1297a53006769e9e861df503144fa7b4ab86a17 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 24 Oct 2022 11:36:06 -0400 Subject: [PATCH 065/162] total_bad_marks array fix --- app/Http/Controllers/Api/V1/AdminController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index e1845975..1c38dbbe 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -568,7 +568,7 @@ function($x, $y) { "daily_earning" => $daily_earning, "total_eras" => $total_eras, "eras_since_bad_mark" => $eras_since_bad_mark, - "total_bad_marks" => count($total_bad_marks ?? array()), + "total_bad_marks" => $total_bad_marks, "failing" => $failing, "validator_rewards" => array( "day" => $earning_day, From ce695acd261606db5de6c5d389736b51f1deee97 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 24 Oct 2022 11:56:12 -0400 Subject: [PATCH 066/162] verification controller upload document mimes --- app/Http/Controllers/Api/V1/VerificationController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/VerificationController.php b/app/Http/Controllers/Api/V1/VerificationController.php index 08c2d92d..cd5370e1 100644 --- a/app/Http/Controllers/Api/V1/VerificationController.php +++ b/app/Http/Controllers/Api/V1/VerificationController.php @@ -110,7 +110,7 @@ public function uploadDocument(Request $request) { // Validator $validator = Validator::make($request->all(), [ 'files' => 'array', - 'files.*' => 'file|max:100000|mimes:pdf,docx,doc,txt,rtf' + 'files.*' => 'file|max:100000|mimes:pdf,jpeg,jpg,png,txt,rtf' ]); if ($validator->fails()) { return $this->validateResponse($validator->errors()); From 46cc2451e339368e7ce04b212c3ac2c0a951effa Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Mon, 24 Oct 2022 21:11:49 -0400 Subject: [PATCH 067/162] # Admin users Update --- .../Controllers/Api/V1/AdminController.php | 42 +++++++++++++++++-- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 1c38dbbe..a32f49a0 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -617,7 +617,8 @@ public function getUsers(Request $request) JOIN all_node_data AS c ON c.public_key = user_addresses.public_address_node */ - $users = DB::select(" + + $query = " SELECT a.id, a.first_name, a.last_name, a.email, a.pseudonym, a.telegram, a.email_verified_at, @@ -632,8 +633,41 @@ public function getUsers(Request $request) LEFT JOIN profile AS b ON a.id = b.user_id WHERE a.role = 'member' - ORDER BY a.id asc - "); + "; + + $sort_key = $request->get('sort_key'); + $sort_direction = $request->get('sort_direction'); + + if ($sort_key && $sort_direction) { + switch ($sort_key) { + case 'id': + $sort_key = 'a.id'; + break; + case 'membership_status': + $sort_key = 'b.status'; + break; + case 'email': + $sort_key = 'a.email'; + break; + case 'entity_name': + $sort_key = 'a.entity_name'; + break; + case 'full_name': + $sort_key = 'a.first_name'; + break; + case 'created_at': + $sort_key = 'a.created_at'; + break; + default: + $sort_key = 'a.id'; + break; + } + $query .= " ORDER BY " . $sort_key . " " . $sort_direction; + } else { + $query .= " ORDER BY a.id asc"; + } + + $users = DB::select($query); if ($users) { foreach ($users as &$user) { @@ -650,7 +684,7 @@ public function getUsers(Request $request) $user->membership_status = $status; } } - // info($users); + return $this->successResponse($users); } From 69e4f80798fc32d3c911564f9c28704a1a4a4eea Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Tue, 25 Oct 2022 09:48:27 -0400 Subject: [PATCH 068/162] # Hellosign Callback --- app/Http/Controllers/Api/V1/HelloSignController.php | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/V1/HelloSignController.php b/app/Http/Controllers/Api/V1/HelloSignController.php index 901ddd8b..e3b0c3f4 100644 --- a/app/Http/Controllers/Api/V1/HelloSignController.php +++ b/app/Http/Controllers/Api/V1/HelloSignController.php @@ -29,7 +29,6 @@ public function hellosignHook(Request $request) // hellosign test check $callback_test = $data['event']['event_type'] ?? ''; - if ($callback_test == 'callback_test') { return "Hello API Event Received"; } @@ -57,16 +56,15 @@ public function hellosignHook(Request $request) // null, // \HelloSign\SignatureRequest::FILE_TYPE_PDF // ); - // info($sig_link); - + $user = User::where('signature_request_id', $signature_request_id)->first(); if ($user) { $user->hellosign_form = ''; $user->save(); } - - return "Hello API Event Received"; } + + return "Hello API Event Received"; } } From 47991f0b8025c849805578c6520c2758e0f1b5a4 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Tue, 25 Oct 2022 10:31:36 -0400 Subject: [PATCH 069/162] # Disable Node-Info --- app/Console/Commands/CheckNodeStatus.php | 4 ++-- app/Console/Kernel.php | 13 ++++++------- app/Http/Controllers/Api/V1/AdminController.php | 1 - app/Services/NodeHelper.php | 2 +- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index 44a3e4a7..a4383c68 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -19,7 +19,7 @@ class CheckNodeStatus extends Command * @var string */ protected $signature = 'node-status:check'; - + /** * The console command description. * @@ -79,7 +79,7 @@ public function handle() ->where('role', 'member') ->where('banned', 0) ->get(); - + foreach ($users as $user) { $user->node_status = 'Online'; $user->save(); diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index c6c45b75..86e968a9 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -25,15 +25,16 @@ class Kernel extends ConsoleKernel protected function schedule(Schedule $schedule) { // $schedule->command('inspire')->hourly(); - $schedule->command('ballot:check') - ->everyMinute() - ->runInBackground(); /* $schedule->command('shuftipro:check') ->everyFiveMinutes() ->runInBackground(); // ->withoutOverlapping(); */ + + $schedule->command('ballot:check') + ->everyMinute() + ->runInBackground(); $schedule->command('perk:check') ->everyThirtyMinutes() ->runInBackground(); @@ -46,16 +47,14 @@ protected function schedule(Schedule $schedule) $schedule->command('token-price:check') ->everyThirtyMinutes() ->runInBackground(); + /* $schedule->command('node-info') ->everyFifteenMinutes() ->runInBackground(); + */ $schedule->command('refresh:address') ->everyFiveMinutes() ->runInBackground(); - - /** - * Added by blockchainthomas. Cron for alerting admins of members stuck at KYC on a daily basis - */ $schedule->command('kyc:report') ->dailyAt('10:02') ->runInBackground(); diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index a32f49a0..ff97e048 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -746,7 +746,6 @@ public function getUserDetail($id) ) ORDER BY era_id DESC "); - $eras_active = DB::select(" SELECT era_id FROM all_node_data2 diff --git a/app/Services/NodeHelper.php b/app/Services/NodeHelper.php index d2db3ca6..f5714b38 100644 --- a/app/Services/NodeHelper.php +++ b/app/Services/NodeHelper.php @@ -340,7 +340,7 @@ public function getValidatorStanding() // get node ips from peers // $port8888_responses = $this->discoverPeers(); $port8888_responses = array(); - + // get auction state from trusted node RPC $curl = curl_init(); From 52016719a635b73ade343256e79356d29018ea80 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Tue, 25 Oct 2022 12:58:40 -0400 Subject: [PATCH 070/162] # Node Status --- app/Console/Commands/CheckNodeStatus.php | 122 ++++++++++++++++++++++- app/Console/Helper.php | 12 +++ app/Console/Kernel.php | 2 +- 3 files changed, 133 insertions(+), 3 deletions(-) diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index a4383c68..65d2babf 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -2,12 +2,17 @@ namespace App\Console\Commands; +use App\Console\Helper; + use App\Models\MonitoringCriteria; use App\Models\User; use App\Models\UserAddress; use App\Models\NodeInfo; +use App\Models\AllNodeData2; + use Carbon\Carbon; +use Illuminate\Support\Facades\DB; use Illuminate\Console\Command; use Illuminate\Support\Facades\Log; @@ -19,7 +24,7 @@ class CheckNodeStatus extends Command * @var string */ protected $signature = 'node-status:check'; - + /** * The console command description. * @@ -43,6 +48,119 @@ public function __construct() * @return int */ public function handle() + { + $settings = Helper::getSettings(); + $current_era_id = Helper::getCurrentERAId(); + + $now = Carbon::now('UTC'); + $users = User::with(['addresses', 'profile']) + ->where('role', 'member') + ->where('banned', 0) + ->get(); + + foreach ($users as $user) { + $addresses = $user->addresses ?? []; + + if ( + !$addresses || + count($addresses) == 0 || + !$user->node_verified_at || + !$user->letter_verified_at || + !$user->signature_request_id + ) { + $user->node_status = null; + $user->save(); + + if ($user->profile) { + $user->profile->extra_status = null; + $user->profile->save(); + } + + if ($addresses && count($addresses) > 0) { + foreach ($addresses as $address) { + $address->node_status = null; + $address->extra_status = null; + $address->save(); + } + } + } else { + $hasOnline = $hasOnProbation = false; + foreach ($addresses as $address) { + $public_address_node = strtolower($address->public_address_node); + + $temp = AllNodeData2::select(['id', 'uptime']) + ->where('public_key', $public_address_node) + ->where('era_id', $current_era_id) + ->where('bid_inactive', 0) + ->where('in_auction', 1) + ->where('in_current_era', 1) + ->first(); + if ($temp) { + $address->node_status = 'Online'; + $address->extra_status = null; + $address->save(); + $hasOnline = true; + + if (isset($settings['uptime_probation']) && (float) $settings['uptime_probation'] > 0) { + $uptime_calc_size = $settings['uptime_calc_size'] ?? 0; + $uptime_calc_size = (int) $uptime_calc_size; + + $uptime = (float) $temp->uptime; + if ($uptime_calc_size > 0) { + $missed = 0; + $in_current_eras = DB::select(" + SELECT in_current_era + FROM all_node_data2 + WHERE public_key = '$public_address_node' + ORDER BY era_id DESC + LIMIT $uptime_calc_size + "); + $in_current_eras = $in_current_eras ? $in_current_eras : []; + $window = $in_current_eras ? count($in_current_eras) : 0; + foreach ($in_current_eras as $c) { + $in = (bool) ($c->in_current_era ?? 0); + if (!$in) { + $missed += 1; + } + } + $uptime = round((float) (($uptime * ($window - $missed)) / $window), 2); + } + + if ($uptime < (float) $settings['uptime_probation']) { + $address->extra_status = 'On Probation'; + $address->save(); + $hasOnProbation = true; + } + } + } else { + $address->node_status = 'Offline'; + $address->extra_status = null; + $address->save(); + } + } + + if ($hasOnline) { + $user->node_status = 'Online'; + $user->save(); + } else { + $user->node_status = 'Offline'; + $user->save(); + } + + if ($user->profile) { + if ($hasOnProbation) { + $user->profile->extra_status = 'On Probation'; + $user->profile->save(); + } else { + $user->profile->extra_status = null; + $user->profile->save(); + } + } + } + } + } + + public function handleOld() { $uptime = MonitoringCriteria::where('type', 'uptime')->first(); $uptimeProbationStart = $uptime->probation_start; @@ -79,7 +197,7 @@ public function handle() ->where('role', 'member') ->where('banned', 0) ->get(); - + foreach ($users as $user) { $user->node_status = 'Online'; $user->save(); diff --git a/app/Console/Helper.php b/app/Console/Helper.php index 8a3b22cb..0c7e40ab 100644 --- a/app/Console/Helper.php +++ b/app/Console/Helper.php @@ -9,6 +9,7 @@ use App\Models\Profile; use App\Models\Shuftipro; use App\Models\User; +use App\Models\Setting; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Http; @@ -22,6 +23,17 @@ class Helper { + public static function getSettings() { + $items = Setting::get(); + $settings = []; + if ($items) { + foreach ($items as $item) { + $settings[$item->name] = $item->value; + } + } + return $settings; + } + public static function getCurrentERAId() { $record = DB::select(" SELECT era_id diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 86e968a9..1a1271b9 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -31,7 +31,7 @@ protected function schedule(Schedule $schedule) ->runInBackground(); // ->withoutOverlapping(); */ - + $schedule->command('ballot:check') ->everyMinute() ->runInBackground(); From b676d88e972e70dc34a89f564941467c16ceb11f Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Tue, 25 Oct 2022 13:22:27 -0400 Subject: [PATCH 071/162] # Admin/users --- .../Controllers/Api/V1/AdminController.php | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index ff97e048..8fa911fa 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -618,12 +618,14 @@ public function getUsers(Request $request) ON c.public_key = user_addresses.public_address_node */ + $current_era_id = Helper::getCurrentERAId(); + $query = " SELECT a.id, a.first_name, a.last_name, a.email, a.pseudonym, a.telegram, a.email_verified_at, a.entity_name, a.last_login_at, a.created_at, - a.signature_request_id, a.node_verified_at, + a.signature_request_id, a.node_status, a.node_verified_at, a.member_status, a.kyc_verified_at, b.dob, b.country_citizenship, b.country_residence, b.status AS profile_status, b.extra_status, @@ -646,6 +648,9 @@ public function getUsers(Request $request) case 'membership_status': $sort_key = 'b.status'; break; + case 'node_status': + $sort_key = 'a.node_status'; + break; case 'email': $sort_key = 'a.email'; break; @@ -682,6 +687,18 @@ public function getUsers(Request $request) } $user->membership_status = $status; + $userId = (int) $user->id; + + $temp = DB::select(" + SELECT sum(a.bid_self_staked_amount) as self_staked_amount + FROM all_node_data2 as a + JOIN user_addresses as b ON b.public_address_node = a.public_key + WHERE b.user_id = $userId and a.era_id = $current_era_id + "); + + $self_staked_amount = 0; + if ($temp && count($temp) > 0) $self_staked_amount = (float) $temp[0]->self_staked_amount; + $user->self_staked_amount = round($self_staked_amount, 2); } } From 0e9f06060cfefa96428b8426354f601d69351813 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Tue, 25 Oct 2022 21:26:12 -0400 Subject: [PATCH 072/162] # Backend Update --- app/Http/Controllers/Api/V1/AdminController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 8fa911fa..43538406 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -1299,7 +1299,7 @@ public function getBallots(Request $request) $startDate = $now->format('Y-m-d'); $startTime = $now->format('H:i:s'); - if ($status == 'active') { + if ($status == 'active') { $ballots = Ballot::with(['user', 'vote']) ->where('ballot.status', 'active') ->where(function ($query) use ($startDate, $startTime) { From 289d42cd262e0cd939608794f00b4d4546dbfeea Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Tue, 25 Oct 2022 21:38:29 -0400 Subject: [PATCH 073/162] # Optional VAT --- app/Http/Controllers/Api/V1/VerificationController.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/V1/VerificationController.php b/app/Http/Controllers/Api/V1/VerificationController.php index cd5370e1..baedda46 100644 --- a/app/Http/Controllers/Api/V1/VerificationController.php +++ b/app/Http/Controllers/Api/V1/VerificationController.php @@ -42,8 +42,7 @@ public function submitNode(Request $request) { 'entity_name' => 'required', 'entity_type' => 'required', 'entity_registration_number' => 'required', - 'entity_registration_country' => 'required', - 'vat_number' => 'required' + 'entity_registration_country' => 'required' ]); if ($validator2->fails()) { return $this->validateResponse($validator2->errors()); @@ -53,7 +52,7 @@ public function submitNode(Request $request) { $profile->entity_type = $request->entity_type; $profile->entity_registration_number = $request->entity_registration_number; $profile->entity_registration_country = $request->entity_registration_country; - $profile->vat_number = $request->vat_number; + $profile->vat_number = $request->vat_number ?? null; } else { $profile->entity_name = null; $profile->entity_type = null; From c2a1f9993facf58834971489809393f7e6516ca3 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 26 Oct 2022 04:50:47 -0400 Subject: [PATCH 074/162] # API Updates --- .../Controllers/Api/V1/AdminController.php | 236 +-------------- .../Controllers/Api/V1/ContactController.php | 8 - .../Controllers/Api/V1/MetricController.php | 280 ------------------ .../Controllers/Api/V1/UserController.php | 74 +---- .../Api/V1/VerificationController.php | 13 +- routes/api.php | 37 +-- 6 files changed, 18 insertions(+), 630 deletions(-) delete mode 100644 app/Http/Controllers/Api/V1/MetricController.php diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 43538406..2a3e8137 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -988,21 +988,6 @@ public function infoDashboard(Request $request) return $this->successResponse($return); } - public function getKYC($id) - { - $user = User::with(['shuftipro', 'profile'])->where('id', $id)->first(); - - if (!$user || $user->role == 'admin') { - return $this->errorResponse( - __('api.error.not_found'), - Response::HTTP_NOT_FOUND - ); - } - - $response = $user->load(['profile', 'shuftipro']); - return $this->successResponse($response); - } - public function bypassApproveKYC($user_id) { $user_id = (int) $user_id; @@ -1963,6 +1948,7 @@ public function resetKYC($id, Request $request) ); } + /* public function refreshLinks($id) { $url = 'https://api.shuftipro.com/status'; @@ -2037,28 +2023,7 @@ public function refreshLinks($id) 'success' => false, ]); } - - public function banAndDenyUser($id) - { - $user = User::with(['shuftipro', 'profile']) - ->where('id', $id) - ->where('users.role', 'member') - ->where('banned', 0) - ->first(); - - if ($user && $user->profileT) { - $user->profile->status = 'denied'; - $user->profile->save(); - $user->banned = 1; - $user->save(); - return $this->metaSuccess(); - } - - return $this->errorResponse( - 'Fail deny and ban user', - Response::HTTP_BAD_REQUEST - ); - } + */ public function getVerificationDetail($id) { @@ -2112,28 +2077,6 @@ public function approveDocument($id) ); } - public function activeUser($id) - { - $user = User::with(['profile']) - ->where('id', $id) - ->where('users.role', 'member') - ->where('banned', 0) - ->first(); - - if ($user && $user->profile) { - $user->profile->status = 'approved'; - $user->profile->save(); - $user->approve_at = now(); - $user->save(); - return $this->metaSuccess(); - } - - return $this->errorResponse( - 'Fail active document', - Response::HTTP_BAD_REQUEST - ); - } - // Add Emailer Admin public function addEmailerAdmin(Request $request) { @@ -2239,40 +2182,6 @@ public function updateEmailerTriggerUser($recordId, Request $request) return ['success' => false]; } - public function getMonitoringCriteria(Request $request) - { - $data = MonitoringCriteria::get(); - return $this->successResponse($data); - } - - public function updateMonitoringCriteria($type, Request $request) - { - $record = MonitoringCriteria::where('type', $type)->first(); - - if ($record) { - $validator = Validator::make($request->all(), [ - 'warning_level' => 'required|integer', - 'probation_start' => 'required', - 'given_to_correct_unit' => 'required|in:Weeks,Days,Hours', - 'given_to_correct_value' => 'required|integer', - ]); - - if ($validator->fails()) { - return $this->validateResponse($validator->errors()); - } - - $record->warning_level = $request->warning_level; - $record->probation_start = $request->probation_start; - $record->given_to_correct_unit = $request->given_to_correct_unit; - $record->given_to_correct_value = $request->given_to_correct_value; - $record->save(); - - return ['success' => true]; - } - - return ['success' => false]; - } - public function updateLockRules(Request $request, $id) { $validator = Validator::make($request->all(), [ @@ -2290,141 +2199,6 @@ public function updateLockRules(Request $request, $id) return ['success' => true]; } - public function getLockRules() - { - $ruleKycNotVerify = LockRules::where('type', 'kyc_not_verify') - ->orderBy('id', 'ASC') - ->select(['id', 'screen', 'is_lock']) - ->get(); - - $ruleStatusIsPoor = LockRules::where('type', 'status_is_poor') - ->orderBy('id', 'ASC') - ->select(['id', 'screen', 'is_lock']) - ->get(); - - $data = [ - 'kyc_not_verify' => $ruleKycNotVerify, - 'status_is_poor' => $ruleStatusIsPoor, - ]; - - return $this->successResponse($data); - } - - public function getListNodes(Request $request) - { - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); - - // define return object - $return = array( - "nodes" => array(), - "ranking" => array(), - "node_rank_total" => 100 - ); - - // find rank - $ranking = DB::select(" - SELECT - public_key, uptime, - bid_delegators_count, - bid_delegation_rate, - bid_total_staked_amount - FROM all_node_data2 - WHERE era_id = $current_era_id - AND in_current_era = 1 - AND in_next_era = 1 - AND in_auction = 1 - "); - $max_delegators = 0; - $max_stake_amount = 0; - - foreach ($ranking as $r) { - if ((int)$r->bid_delegators_count > $max_delegators) { - $max_delegators = (int)$r->bid_delegators_count; - } - - if ((int)$r->bid_total_staked_amount > $max_stake_amount) { - $max_stake_amount = (int)$r->bid_total_staked_amount; - } - } - - foreach ($ranking as $r) { - $uptime_score = ( - 25 * (float)$r->uptime - ) / 100; - $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; - - $fee_score = ( - 25 * - (1 - ((float)$r->bid_delegation_rate / 100)) - ); - $fee_score = $fee_score < 0 ? 0 : $fee_score; - - $count_score = ( - (float)$r->bid_delegators_count / - $max_delegators - ) * 25; - $count_score = $count_score < 0 ? 0 : $count_score; - - $stake_score = ( - (float)$r->bid_total_staked_amount / - $max_stake_amount - ) * 25; - $stake_score = $stake_score < 0 ? 0 : $stake_score; - - $return["ranking"][$r->public_key] = ( - $uptime_score + - $fee_score + - $count_score + - $stake_score - ); - } - - uasort( - $return["ranking"], - function($x, $y) { - if ($x == $y) { - return 0; - } - - return ($x > $y) ? -1 : 1; - } - ); - - $sorted_ranking = array(); - $i = 1; - - foreach ($return["ranking"] as $public_key => $score) { - $sorted_ranking[$public_key] = $i; - $i += 1; - } - - $return["ranking"] = $sorted_ranking; - $return["node_rank_total"] = count($sorted_ranking); - - $return["nodes"] = DB::select(" - SELECT - a.public_address_node, - b.id, - b.pseudonym, - c.blockchain_name, - c.blockchain_desc - FROM user_addresses AS a - JOIN users AS b - ON a.user_id = b.id - JOIN profile AS c - ON b.id = c.user_id - WHERE b.banned = 0 - "); - // info($nodes); - return $this->successResponse($return); - } - // Get GraphInfo public function getGraphInfo(Request $request) { @@ -2543,10 +2317,4 @@ public function uploadMembershipFile(Request $request) ); } } - - public function getMembershipFile() - { - $membershipAgreementFile = MembershipAgreementFile::first(); - return $this->successResponse($membershipAgreementFile); - } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/V1/ContactController.php b/app/Http/Controllers/Api/V1/ContactController.php index 494a9b41..06708977 100644 --- a/app/Http/Controllers/Api/V1/ContactController.php +++ b/app/Http/Controllers/Api/V1/ContactController.php @@ -42,14 +42,6 @@ public function submitContact(Request $request) return $this->metaSuccess(); } - public function getContactRecipients(Request $request) - { - $sort_key = $request->sort_key ?? 'created_at'; - $sort_direction = $request->sort_direction ?? 'desc'; - $contactRecipients = ContactRecipient::orderBy($sort_key, $sort_direction)->get(); - return $this->successResponse($contactRecipients); - } - public function addContactRecipients(Request $request) { $validator = Validator::make($request->all(), [ diff --git a/app/Http/Controllers/Api/V1/MetricController.php b/app/Http/Controllers/Api/V1/MetricController.php deleted file mode 100644 index bace0061..00000000 --- a/app/Http/Controllers/Api/V1/MetricController.php +++ /dev/null @@ -1,280 +0,0 @@ -user()->load(['pagePermissions']); - $user_id = $user->id; - - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); - - $addresses = DB::select(" - SELECT - a.public_key, a.bid_delegators_count AS delegators, - a.bid_total_staked_amount, a.bid_self_staked_amount, - a.uptime, a.bid_inactive, a.in_current_era, a.in_auction, - a.port8888_peers AS peers - FROM all_node_data2 AS a - JOIN user_addresses AS b - ON b.public_address_node = a.public_key - JOIN users AS c - ON c.id = b.user_id - WHERE a.era_id = $current_era_id - AND b.user_id = $user_id - "); - - if (!$addresses) { - $addresses = array(); - } - - $return_object['addresses'] = $addresses; - - foreach ($addresses as &$address) { - $a = $address->public_key; - - $total_bad_marks = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$a' - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - ORDER BY era_id DESC - "); - - $eras_since_bad_mark = $total_bad_marks[0] ?? array(); - $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); - $total_bad_marks = count((array)$total_bad_marks); - - $address->eras_since_bad_mark = $eras_since_bad_mark; - $address->total_bad_marks = $total_bad_marks; - } - - $monitoring_criteria = DB::select(" - SELECT * - FROM monitoring_criteria - "); - $return_object['monitoring_criteria'] = $monitoring_criteria; - - $items = Setting::get(); - $settings = []; - if ($items) { - foreach ($items as $item) { - $settings[$item->name] = $item->value; - } - } - $return_object['globalSettings'] = $settings; - - return $this->successResponse($return_object); - } - - public function updateMetric(Request $request, $id) - { - $validator = Validator::make($request->all(), [ - 'uptime' => 'nullable|numeric|between:0,100', - ]); - - if ($validator->fails()) { - return $this->validateResponse($validator->errors()); - } - - $user = User::where('id', $id)->where('role', 'member')->first(); - - if (!$user) { - return $this->errorResponse( - 'User not found', - Response::HTTP_BAD_REQUEST - ); - } - - $metric = Metric::where('user_id', $id)->first(); - - if (!$metric) { - $metric = new Metric(); - } - - if ( - isset($request->uptime) && $request->uptime != null - ) { - $metric->uptime = $request->uptime; - } - - if ( - isset($request->block_height_average) && - $request->block_height_average != null - ) { - $metric->block_height_average = $request->block_height_average; - } - - if ( - isset($request->update_responsiveness) && - $request->update_responsiveness != null - ) { - $metric->update_responsiveness = $request->update_responsiveness; - } - - if ( - isset($request->peers) && - $request->peers != null - ) { - $metric->peers = $request->peers; - } - - $metric->user_id = $id; - $metric->save(); - return $this->successResponse($metric); - } - - public function getMetricUser($id) - { - $user = User::find($id); - - if (!$user) { - return $this->successResponse([]); - } - - $return_object = array(); - $user_id = $user->id; - - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); - - $addresses = DB::select(" - SELECT - a.public_key, a.uptime, - a.port8888_peers AS peers, - a.bid_inactive, a.in_current_era - FROM all_node_data2 AS a - JOIN user_addresses AS b - ON a.public_key = b.public_address_node - WHERE a.era_id = $current_era_id - AND b.user_id = $user_id - "); - $return_object['addresses'] = $addresses; - - foreach ($addresses as &$address) { - $p = $address->public_key; - - $total_bad_marks = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - ORDER BY era_id DESC - "); - - $eras_since_bad_mark = $total_bad_marks[0] ?? array(); - $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); - $total_bad_marks = count((array)$total_bad_marks); - - $address->eras_since_bad_mark = $eras_since_bad_mark; - $address->total_bad_marks = $total_bad_marks; - $address->update_responsiveness = 100; - } - - $monitoring_criteria = DB::select(" - SELECT * - FROM monitoring_criteria - "); - $return_object['monitoring_criteria'] = $monitoring_criteria; - return $this->successResponse($return_object); - } - - public function getMetricUserByNodeName($node) - { - $node = strtolower($node); - $return_object = array(); - - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); - - $addresses = DB::select(" - SELECT - a.public_key, a.uptime, - a.bid_delegators_count AS delegators, - a.port8888_peers AS peers, - a.bid_inactive, a.in_current_era, - a.bid_self_staked_amount, a.bid_total_staked_amount, - c.node_status - FROM all_node_data2 AS a - JOIN user_addresses AS b - ON a.public_key = b.public_address_node - JOIN users AS c - ON b.user_id = c.id - WHERE a.era_id = $current_era_id - AND b.public_address_node = '$node' - "); - $return_object['addresses'] = $addresses; - - foreach ($addresses as &$address) { - $p = $address->public_key; - - $total_bad_marks = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - ORDER BY era_id DESC - "); - - $eras_since_bad_mark = $total_bad_marks[0] ?? array(); - $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); - - $address->eras_since_bad_mark = $eras_since_bad_mark; - } - - $monitoring_criteria = DB::select(" - SELECT * - FROM monitoring_criteria - "); - $return_object['monitoring_criteria'] = $monitoring_criteria; - // info($return_object); - return $this->successResponse($return_object); - } -} \ No newline at end of file diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 528f1397..a404a227 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1006,52 +1006,6 @@ public function getMyEras() { return $this->successResponse($return); } - public function getMemberCountInfo() { - $data = [ - 'total' => 0, - 'verified' => 0, - ]; - - $data['total'] = User::count(); - $data['verified'] = User::join('profile', 'profile.user_id', '=', 'users.id') - ->where('profile.status', 'approved') - ->whereNotNull('users.public_address_node') - ->get() - ->count(); - return $this->successResponse($data); - } - - // Get Verified Members - public function getVerifiedMembers(Request $request) { - $user = auth()->user(); - - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); - - $members = DB::select(" - SELECT - a.public_key, - c.id, c.pseudonym, c.node_status, - d.extra_status - FROM all_node_data2 AS a - JOIN user_addresses AS b - ON a.public_key = b.public_address_node - JOIN users AS c - ON b.user_id = c.id - JOIN profile AS d - ON c.id = d.user_id - WHERE d.status = 'approved' - AND a.era_id = $current_era_id - "); - // info($members); - return $this->successResponse($members); - } - // Shuftipro Webhook public function updateShuftiproStatus() { $json = file_get_contents('php://input'); @@ -1724,18 +1678,6 @@ public function functionSubmitKYC(SubmitKYCRequest $request) return $this->metaSuccess(); } - public function verifyOwnerNode(Request $request) - { - $user = auth()->user(); - - $this->profileRepo->updateConditions( - ['type_owner_node' => $request->type], - ['user_id' => $user->id] - ); - - return $this->metaSuccess(); - } - public function getOwnerNodes() { $user = auth()->user(); @@ -1843,6 +1785,7 @@ public function deleteShuftiproTemp(Request $request) } // Update Shuftipro Temp Status + /* public function updateShuftiProTemp(Request $request) { $user = auth()->user(); @@ -1887,6 +1830,7 @@ public function updateShuftiProTemp(Request $request) Response::HTTP_BAD_REQUEST ); } + */ // get vote list public function getVotes(Request $request) @@ -3035,6 +2979,7 @@ public function getListNodesBy(Request $request) ]); } + /* public function getListNodes(Request $request) { $user = auth()->user(); @@ -3173,6 +3118,7 @@ function($x, $y) { return $this->successResponse($nodes); } + */ public function infoDashboard() { @@ -3205,6 +3151,7 @@ public function infoDashboard() return $this->successResponse($response); } + /* public function getEarningByNode($node) { $node = strtolower($node); @@ -3263,7 +3210,9 @@ public function getEarningByNode($node) ]); } } + */ + /* public function getChartEarningByNode($node) { $node = strtolower($node); @@ -3286,6 +3235,7 @@ public function getChartEarningByNode($node) return $this->successResponse(null); } } + */ public function getMembershipFile() { @@ -3300,12 +3250,4 @@ public function membershipAgreement() $user->save(); return $this->metaSuccess(); } - - public function checkResetKyc() - { - $user = auth()->user(); - $user->reset_kyc = 0; - $user->save(); - return $this->metaSuccess(); - } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/V1/VerificationController.php b/app/Http/Controllers/Api/V1/VerificationController.php index baedda46..cdbd2612 100644 --- a/app/Http/Controllers/Api/V1/VerificationController.php +++ b/app/Http/Controllers/Api/V1/VerificationController.php @@ -175,15 +175,4 @@ public function uploadDocument(Request $request) { return $this->errorResponse(__('Failed upload file'), Response::HTTP_BAD_REQUEST, $ex->getMessage()); } } - - public function removeDocument($id) { - $user = auth()->user(); - $documentFile = DocumentFile::where('user_id', $user->id)->where('id', $id)->first(); - if ($documentFile) { - Storage::delete($documentFile->path); - $documentFile->delete(); - } - $response = DocumentFile::where('user_id', $user->id)->get(); - return $this->successResponse($response); - } -} +} \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index 5a167ba8..b4aef59f 100644 --- a/routes/api.php +++ b/routes/api.php @@ -6,7 +6,6 @@ use App\Http\Controllers\Api\V1\HelloSignController; use App\Http\Controllers\Api\V1\UserController; use App\Http\Controllers\Api\V1\DiscussionController; -use App\Http\Controllers\Api\V1\MetricController; use App\Http\Controllers\Api\V1\NotificationController; use App\Http\Controllers\Api\V1\PerkController; use App\Http\Controllers\Api\V1\VerificationController; @@ -70,7 +69,7 @@ // New endpoint for User voting eligibility check Route::get('/users/can-vote', [UserController::class, 'canVote']); - + Route::post('/users/verify-email', [AuthController::class, 'verifyEmail']); Route::post('/users/resend-verify-email', [AuthController::class, 'resendVerifyEmail']); Route::post('/users/change-email', [UserController::class, 'changeEmail']); @@ -83,12 +82,11 @@ Route::post('users/verify-file-casper-signer', [UserController::class, 'verifyFileCasperSigner']); Route::post('users/verify-file-casper-signer-2', [UserController::class, 'verifyFileCasperSigner2']); Route::post('users/submit-kyc', [UserController::class, 'functionSubmitKYC']); - Route::post('users/verify-owner-node', [UserController::class, 'verifyOwnerNode']); Route::get('users/owner-node', [UserController::class, 'getOwnerNodes']); Route::post('users/resend-invite-owner', [UserController::class, 'resendEmailOwnerNodes']); Route::get('users/message-content', [UserController::class, 'getMessageContent']); Route::post('users/shuftipro-temp', [UserController::class, 'saveShuftiproTemp']); - Route::put('users/shuftipro-temp', [UserController::class, 'updateShuftiproTemp']); + // Route::put('users/shuftipro-temp', [UserController::class, 'updateShuftiproTemp']); Route::put('users/shuftipro-temp/delete', [UserController::class, 'deleteShuftiproTemp']); Route::post('/users/upload-letter', [UserController::class, 'uploadLetter']); Route::get('users/votes', [UserController::class, 'getVotes']); @@ -100,7 +98,6 @@ Route::post('/users/check-password', [UserController::class, 'checkCurrentPassword']); Route::post('/users/settings', [UserController::class, 'settingUser']); - Route::get('/users/metrics', [MetricController::class, 'getMetric']); Route::post('/users/check-login-2fa', [UserController::class, 'checkLogin2FA']); Route::post('/users/resend-2fa', [UserController::class, 'resend2FA']); Route::get('/users/notification', [NotificationController::class, 'getNotificationUser']); @@ -110,17 +107,17 @@ // rules lock Route::get('/users/lock-rules', [UserController::class, 'getLockRules']); - Route::get('users/list-node', [UserController::class, 'getListNodes']); + // Route::get('users/list-node', [UserController::class, 'getListNodes']); Route::get('users/list-node-by', [UserController::class, 'getListNodesBy']); Route::get('users/dashboard', [UserController::class, 'infoDashboard']); - Route::get('/nodes/{node}/earning', [UserController::class, 'getEarningByNode']); - Route::get('/nodes/{node}/chart', [UserController::class, 'getChartEarningByNode']); + + // Route::get('/nodes/{node}/earning', [UserController::class, 'getEarningByNode']); + // Route::get('/nodes/{node}/chart', [UserController::class, 'getChartEarningByNode']); Route::post('/users/contact-us', [ContactController::class, 'submitContact']); Route::get('/users/membership-file', [UserController::class, 'getMembershipFile']); Route::post('/users/membership-agreement', [UserController::class, 'membershipAgreement']); - Route::post('/users/check-reset-kyc', [UserController::class, 'checkResetKyc']); }); Route::prefix('admin')->middleware(['role_admin'])->group(function () { @@ -139,8 +136,6 @@ Route::get('/users', [AdminController::class, 'getUsers']); Route::get('/users/{id}', [AdminController::class, 'getUserDetail'])->where('id', '[0-9]+'); Route::get('/dashboard', [AdminController::class, 'infoDashboard']); - Route::get('/users/{id}/kyc', [AdminController::class, 'getKYC'])->where('id', '[0-9]+'); - Route::get('/list-node', [AdminController::class, 'getListNodes']); // intakes Route::middleware([])->group(function () { @@ -149,7 +144,6 @@ Route::post('/users/intakes/{id}/reset', [AdminController::class, 'resetIntakeUser'])->where('id', '[0-9]+'); Route::post('/users/{id}/ban', [AdminController::class, 'banUser'])->where('id', '[0-9]+'); Route::post('/users/{id}/remove', [AdminController::class, 'removeUser'])->where('id', '[0-9]+'); - Route::post('/users/{id}/refresh-links', [AdminController::class, 'refreshLinks'])->where('id', '[0-9]+'); }); // user @@ -157,9 +151,7 @@ Route::get('/users/verification', [AdminController::class, 'getVerificationUsers']); Route::get('/users/verification/{id}', [AdminController::class, 'getVerificationDetail'])->where('id', '[0-9]+'); Route::post('/users/{id}/reset-kyc', [AdminController::class, 'resetKYC'])->where('id', '[0-9]+'); - Route::post('/users/{id}/deny-ban', [AdminController::class, 'banAndDenyUser'])->where('id', '[0-9]+'); Route::post('/users/{id}/approve-document', [AdminController::class, 'approveDocument'])->where('id', '[0-9]+'); - Route::post('/users/{id}/active', [AdminController::class, 'activeUser'])->where('id', '[0-9]+'); }); // ballots @@ -204,14 +196,6 @@ Route::put('/emailer-trigger-admin/{recordId}', [AdminController::class, 'updateEmailerTriggerAdmin']); Route::put('/emailer-trigger-user/{recordId}', [AdminController::class, 'updateEmailerTriggerUser']); - // metrics - Route::get('/metrics/{id}', [MetricController::class, 'getMetricUser']); - Route::put('/metrics/{id}', [MetricController::class, 'updateMetric']); - Route::get('/node/{node}', [MetricController::class, 'getMetricUserByNodeName']); - - Route::get('/monitoring-criteria', [AdminController::class, 'getMonitoringCriteria']); - Route::put('/monitoring-criteria/{type}', [AdminController::class, 'updateMonitoringCriteria']); - Route::get('/notification/{id}', [NotificationController::class, 'getNotificationDetail'])->where('id', '[0-9]+'); Route::put('/notification/{id}', [NotificationController::class, 'updateNotification'])->where('id', '[0-9]+'); Route::post('/notification', [NotificationController::class, 'createNotification']); @@ -220,23 +204,17 @@ Route::get('/notification/high-priority', [NotificationController::class, 'getHighPriority']); // rules lock - Route::get('/lock-rules', [AdminController::class, 'getLockRules']); Route::put('/lock-rules/{id}', [AdminController::class, 'updateLockRules'])->where('id', '[0-9]+'); // contact recipients - Route::get('/contact-recipients', [ContactController::class, 'getContactRecipients']); Route::post('/contact-recipients', [ContactController::class, 'addContactRecipients']); Route::delete('/contact-recipients/{id}', [ContactController::class, 'deleteContactRecipients'])->where('id', '[0-9]+'); - Route::get('/membership-file', [AdminController::class, 'getMembershipFile']); Route::post('/membership-file', [AdminController::class, 'uploadMembershipFile']); - + // Block Access Route::post('/block-access', [BlockAccessController::class, 'updateBlockAccess']); }); - Route::get('/verified-members/all', [UserController::class, 'getVerifiedMembers']); - Route::get('/member-count-info', [UserController::class, 'getMemberCountInfo']); - Route::prefix('discussions')->group(function () { Route::get('/trending', [DiscussionController::class, 'getTrending']); Route::get('/all', [DiscussionController::class, 'getDiscussions']); @@ -260,7 +238,6 @@ Route::post('/submit-node', [VerificationController::class, 'submitNode']); Route::post('/submit-detail', [VerificationController::class, 'submitDetail']); Route::post('/upload-document', [VerificationController::class, 'uploadDocument']); - Route::delete('/remove-document/{id}', [VerificationController::class, 'removeDocument']); }); Route::prefix('perks')->group(function () { From fc324a4bcc2d1376842b88696d3d782c65a749aa Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 26 Oct 2022 05:24:31 -0400 Subject: [PATCH 075/162] # Unit Test --- .../Controllers/Api/V1/UserController.php | 67 ----------- routes/api.php | 8 +- tests/Feature/AdminFunctionsTest.php | 37 +----- tests/Feature/BlockAccessFunctionsTest.php | 48 -------- tests/Feature/UserFunctionsTest.php | 110 +----------------- 5 files changed, 7 insertions(+), 263 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index a404a227..79616cf3 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -9,7 +9,6 @@ use App\Http\Requests\Api\AddOwnerNodeRequest; use App\Http\Requests\Api\ChangeEmailRequest; -use App\Http\Requests\Api\ChangePasswordRequest; use App\Http\Requests\Api\ResendEmailRequest; use App\Http\Requests\Api\SubmitKYCRequest; use App\Http\Requests\Api\SubmitPublicAddressRequest; @@ -1098,16 +1097,6 @@ public function changeEmail(ChangeEmailRequest $request) } } - public function changePassword(ChangePasswordRequest $request) - { - $user = auth()->user(); - if (Hash::check($request->new_password, $user->password)) - return $this->errorResponse(__('api.error.not_same_current_password'), Response::HTTP_BAD_REQUEST); - $newPassword = bcrypt($request->new_password); - $user->update(['password' => $newPassword]); - return $this->metaSuccess(); - } - public function getProfile() { $user = auth()->user()->load(['profile', 'pagePermissions', 'permissions', 'shuftipro', 'shuftiproTemp']); @@ -1126,12 +1115,6 @@ public function getProfile() return $this->successResponse($user); } - public function logout() - { - auth()->user()->token()->revoke(); - return $this->metaSuccess(); - } - public function uploadLetter(Request $request) { try { @@ -1678,56 +1661,6 @@ public function functionSubmitKYC(SubmitKYCRequest $request) return $this->metaSuccess(); } - public function getOwnerNodes() - { - $user = auth()->user(); - $owners = OwnerNode::where('user_id', $user->id)->get(); - - foreach ($owners as $owner) { - $email = $owner->email; - $userOwner = User::where('email', $email)->first(); - - if ($userOwner) { - $owner->kyc_verified_at = $userOwner->kyc_verified_at; - } else { - $owner->kyc_verified_at = null; - } - } - - $data = []; - $data['kyc_verified_at'] = $user->kyc_verified_at; - $data['owner_node'] = $owners; - - return $this->successResponse($data); - } - - public function resendEmailOwnerNodes(ResendEmailRequest $request) - { - $user = auth()->user(); - $email = $request->email; - $owners = OwnerNode::where('user_id', $user->id) - ->where('email', $email) - ->first(); - - if ($owners) { - $userOwner = User::where('email', $email)->first(); - - if (!$userOwner) { - $url = $request->header('origin') ?? $request->root(); - $resetUrl = $url . '/register-type'; - - Mail::to($email)->send(new AddNodeMail($resetUrl)); - } - } else { - return $this->errorResponse( - 'Email does not exist', - Response::HTTP_BAD_REQUEST - ); - } - - return $this->successResponse(null); - } - // Save Shuftipro Temp public function saveShuftiproTemp(Request $request) { diff --git a/routes/api.php b/routes/api.php index b4aef59f..a6c7471b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -69,24 +69,19 @@ // New endpoint for User voting eligibility check Route::get('/users/can-vote', [UserController::class, 'canVote']); - + Route::post('/users/verify-email', [AuthController::class, 'verifyEmail']); Route::post('/users/resend-verify-email', [AuthController::class, 'resendVerifyEmail']); Route::post('/users/change-email', [UserController::class, 'changeEmail']); - Route::post('/users/change-password', [UserController::class, 'changePassword']); Route::get('/users/profile', [UserController::class, 'getProfile']); - Route::post('/users/logout', [UserController::class, 'logout']); Route::post('users/hellosign-request', [UserController::class, 'sendHellosignRequest']); Route::post('users/submit-public-address', [UserController::class, 'submitPublicAddress']); Route::post('users/check-public-address', [UserController::class, 'checkPublicAddress']); Route::post('users/verify-file-casper-signer', [UserController::class, 'verifyFileCasperSigner']); Route::post('users/verify-file-casper-signer-2', [UserController::class, 'verifyFileCasperSigner2']); Route::post('users/submit-kyc', [UserController::class, 'functionSubmitKYC']); - Route::get('users/owner-node', [UserController::class, 'getOwnerNodes']); - Route::post('users/resend-invite-owner', [UserController::class, 'resendEmailOwnerNodes']); Route::get('users/message-content', [UserController::class, 'getMessageContent']); Route::post('users/shuftipro-temp', [UserController::class, 'saveShuftiproTemp']); - // Route::put('users/shuftipro-temp', [UserController::class, 'updateShuftiproTemp']); Route::put('users/shuftipro-temp/delete', [UserController::class, 'deleteShuftiproTemp']); Route::post('/users/upload-letter', [UserController::class, 'uploadLetter']); Route::get('users/votes', [UserController::class, 'getVotes']); @@ -107,7 +102,6 @@ // rules lock Route::get('/users/lock-rules', [UserController::class, 'getLockRules']); - // Route::get('users/list-node', [UserController::class, 'getListNodes']); Route::get('users/list-node-by', [UserController::class, 'getListNodesBy']); Route::get('users/dashboard', [UserController::class, 'infoDashboard']); diff --git a/tests/Feature/AdminFunctionsTest.php b/tests/Feature/AdminFunctionsTest.php index 63dde58c..2ed3dc49 100644 --- a/tests/Feature/AdminFunctionsTest.php +++ b/tests/Feature/AdminFunctionsTest.php @@ -8,6 +8,7 @@ class AdminFunctionsTest extends TestCase { + /* public function testConsoleCommand() { $node = '011117189c666f81c5160cd610ee383dc9b2d0361f004934754d39752eedc64957'; $token = $this->getUserToken(); @@ -23,6 +24,7 @@ public function testConsoleCommand() { $this->artisan('node-info')->assertSuccessful(); } + */ public function testGetGraphInfo() { $response = $this->withHeaders([ @@ -90,41 +92,6 @@ public function testGetInfoDashboard() { ]); } - public function testGetKYC() { - $token = $this->getAdminToken(); - $user = $this->addUser(); - - $response = $this->withHeaders([ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer ' . $token, - ])->json('get', '/api/v1/admin/users/' . $user->id . '/kyc'); - - // $apiResponse = $response->baseResponse->getData(); - - $response->assertStatus(200) - ->assertJsonStructure([ - 'message', - 'data', - ]); - } - - public function testListNode() { - $token = $this->getAdminToken(); - - $response = $this->withHeaders([ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer ' . $token, - ])->json('get', '/api/v1/admin/list-node'); - - // $apiResponse = $response->baseResponse->getData(); - - $response->assertStatus(200) - ->assertJsonStructure([ - 'message', - 'data', - ]); - } - public function testGetIntakes() { $token = $this->getAdminToken(); diff --git a/tests/Feature/BlockAccessFunctionsTest.php b/tests/Feature/BlockAccessFunctionsTest.php index 1c88a999..7b48168a 100644 --- a/tests/Feature/BlockAccessFunctionsTest.php +++ b/tests/Feature/BlockAccessFunctionsTest.php @@ -32,54 +32,6 @@ public function testUpdateBlockAccess() { ]); } - public function testGetMetric() { - $tokenData = $this->getUserTokenData(); - $user = $tokenData['user']; - $token = $tokenData['token']; - - $this->unBlockAccess($user->id, 'nodes'); - - $response = $this->withHeaders([ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer ' . $token, - ])->json('get', '/api/v1/users/metrics'); - - $apiResponse = $response->baseResponse->getData(); - $response->assertStatus(200) - ->assertJsonStructure([ - 'message', - 'data', - ]); - - $data = $apiResponse->data; - $this->assertTrue(is_object($data) && property_exists($data, 'avg_uptime')); - } - - /* - public function testBlockedGetMetric() { - $tokenData = $this->getUserTokenData(); - $user = $tokenData['user']; - $token = $tokenData['token']; - - $this->blockAccess($user->id, 'nodes'); - - $response = $this->withHeaders([ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer ' . $token, - ])->json('get', '/api/v1/users/metrics'); - - $apiResponse = $response->baseResponse->getData(); - $response->assertStatus(200) - ->assertJsonStructure([ - 'message', - 'data', - ]); - - $data = $apiResponse->data; - $this->assertTrue(!(is_object($data) && property_exists($data, 'avg_uptime'))); - } - */ - public function testGetPerks() { $tokenData = $this->getUserTokenData(); $user = $tokenData['user']; diff --git a/tests/Feature/UserFunctionsTest.php b/tests/Feature/UserFunctionsTest.php index d0669282..fcc68a05 100644 --- a/tests/Feature/UserFunctionsTest.php +++ b/tests/Feature/UserFunctionsTest.php @@ -153,27 +153,6 @@ public function testChangeEmail() { 'data', ]); } - - public function testChangePassword() { - $token = $this->getUserToken(); - - $params = [ - 'new_password' => 'TestIndividual111New@', - ]; - - $response = $this->withHeaders([ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer ' . $token, - ])->json('post', '/api/v1/users/change-password', $params); - - // $apiResponse = $response->baseResponse->getData(); - - $response->assertStatus(200) - ->assertJsonStructure([ - 'message', - 'data', - ]); - } public function testGetProfile() { $token = $this->getUserToken(); @@ -192,23 +171,6 @@ public function testGetProfile() { ]); } - public function testLogout() { - $token = $this->getUserToken(); - - $response = $this->withHeaders([ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer ' . $token, - ])->json('post', '/api/v1/users/logout'); - - // $apiResponse = $response->baseResponse->getData(); - - $response->assertStatus(200) - ->assertJsonStructure([ - 'message', - 'data', - ]); - } - public function testSubmitPublicAddress() { $token = $this->getUserToken(); @@ -467,23 +429,6 @@ public function testUploadLetter() { ]); } - public function testListNodes() { - $token = $this->getUserToken(); - - $response = $this->withHeaders([ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer ' . $token, - ])->json('get', '/api/v1/users/list-node'); - - // $apiResponse = $response->baseResponse->getData(); - - $response->assertStatus(200) - ->assertJsonStructure([ - 'message', - 'data', - ]); - } - public function testListNodesBy() { $token = $this->getUserToken(); @@ -535,6 +480,7 @@ public function testInfoDashboard() { ]); } + /* public function testGetEarningByNode() { $node = '011117189c666f81c5160cd610ee383dc9b2d0361f004934754d39752eedc64957'; $token = $this->getUserToken($node); @@ -552,7 +498,9 @@ public function testGetEarningByNode() { 'data', ]); } + */ + /* public function testGetChartEarningByNode() { $node = '011117189c666f81c5160cd610ee383dc9b2d0361f004934754d39752eedc64957'; $token = $this->getUserToken($node); @@ -570,57 +518,7 @@ public function testGetChartEarningByNode() { 'data', ]); } - - public function testGetMemberCountInfo() { - $token = $this->getUserToken(); - - $response = $this->withHeaders([ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer ' . $token, - ])->json('get', '/api/v1/member-count-info'); - - // $apiResponse = $response->baseResponse->getData(); - - $response->assertStatus(200) - ->assertJsonStructure([ - 'message', - 'data', - ]); - } - - public function testGetVerifiedMembers() { - $token = $this->getUserToken(); - - $response = $this->withHeaders([ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer ' . $token, - ])->json('get', '/api/v1/verified-members/all'); - - // $apiResponse = $response->baseResponse->getData(); - - $response->assertStatus(200) - ->assertJsonStructure([ - 'message', - 'data', - ]); - } - - public function testCheckResetKYC() { - $token = $this->getUserToken(); - - $response = $this->withHeaders([ - 'Accept' => 'application/json', - 'Authorization' => 'Bearer ' . $token, - ])->json('post', '/api/v1/users/check-reset-kyc'); - - // $apiResponse = $response->baseResponse->getData(); - - $response->assertStatus(200) - ->assertJsonStructure([ - 'message', - 'data', - ]); - } + */ public function testMembershipAgreement() { $token = $this->getUserToken(); From db62eea0a6a9b5514a3a17f9785ad4e9966b2744 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 26 Oct 2022 05:31:44 -0400 Subject: [PATCH 076/162] # Updates --- app/Http/Controllers/Api/V1/AuthController.php | 4 +++- routes/api.php | 2 +- tests/Feature/BlockAccessFunctionsTest.php | 5 ----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AuthController.php b/app/Http/Controllers/Api/V1/AuthController.php index 8a632c4a..1f696e0b 100644 --- a/app/Http/Controllers/Api/V1/AuthController.php +++ b/app/Http/Controllers/Api/V1/AuthController.php @@ -61,6 +61,7 @@ public function testHash() { exit(Hash::make('ledgerleapllc')); } + /* public function devVerifyNode($address) { $query = DB::select(" @@ -102,6 +103,7 @@ public function devVerifyNode($address) return $this->metaSuccess(); } + */ /** * Auth user function @@ -422,4 +424,4 @@ private function checCode($verifyUser) { return ($verifyUser && $verifyUser->created_at >= now()->subMinutes(VerifyUser::TOKEN_LIFETIME)); } -} +} \ No newline at end of file diff --git a/routes/api.php b/routes/api.php index a6c7471b..5455f065 100644 --- a/routes/api.php +++ b/routes/api.php @@ -26,7 +26,7 @@ */ //// REMOVE -Route::get('/dev-verify-node/{address}', [AuthController::class, 'devVerifyNode'])->where('address', '[0-9a-zA-Z]+'); +// Route::get('/dev-verify-node/{address}', [AuthController::class, 'devVerifyNode'])->where('address', '[0-9a-zA-Z]+'); Route::namespace('Api')->middleware([])->group(function () { Route::post('hellosign', [HelloSignController::class, 'hellosignHook']); diff --git a/tests/Feature/BlockAccessFunctionsTest.php b/tests/Feature/BlockAccessFunctionsTest.php index 7b48168a..09ddd2f1 100644 --- a/tests/Feature/BlockAccessFunctionsTest.php +++ b/tests/Feature/BlockAccessFunctionsTest.php @@ -78,11 +78,6 @@ public function testBlockedGetPerks() { $this->assertTrue(!(is_object($data) && property_exists($data, 'current_page'))); } - /* - public function testCreatePerk() { - } - */ - public function testBlockedGetPerksDetail() { $tokenData = $this->getUserTokenData(); $user = $tokenData['user']; From 950e3281ba6c5fff4017740bc81be1f158df0dcc Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 26 Oct 2022 09:15:36 -0400 Subject: [PATCH 077/162] # Update --- .../Controllers/Api/V1/UserController.php | 52 +++++-------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 79616cf3..4d74d613 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -93,38 +93,14 @@ public function __construct( $this->failed_verification_response = 'Failed verification'; } - /** - * - * Get all data required to populate user dashboard - * - * 1. Rank out of Total - * 2. Total stake across all user's nodes - * 3. Total self stake across all user's nodes - * 4. Total delegators across all user's nodes - * 5. New avg uptime for all user's node - * 6. ERAs active for oldest user's node - * 7. ERAs sinse bad mark across all user's nodes - * 8. Total bad marks across all user's nodes - * 9. Update Responsiveness for main node - * 10. Total peers across all nodes - * 11. Association members, verified/total counts - * - */ public function getUserDashboard() { $user = auth()->user(); $user_id = $user->id ?? 0; - // Get current era - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + $current_era_id = Helper::getCurrentERAId(); // Define complete return object - $return = array( + $return = [ "node_rank" => 0, "node_rank_total" => 100, "total_stake" => 0, @@ -138,9 +114,9 @@ public function getUserDashboard() { "peers" => 0, "total_members" => 0, "verified_members" => 0, - "association_members" => array(), - "ranking" => array() - ); + "association_members" => [], + "ranking" => [] + ]; // get all active members $association_members = DB::select(" @@ -155,17 +131,15 @@ public function getUserDashboard() { ON b.user_id = c.id JOIN profile AS d ON c.id = d.user_id - WHERE a.era_id = $current_era_id + WHERE a.era_id = $current_era_id and d.status = 'approved' "); if (!$association_members) { - $association_members = array(); + $association_members = []; } foreach ($association_members as $member) { - if ($member->status == 'approved') { - $return["association_members"][] = $member; - } + $return["association_members"][] = $member; } // get verified members count @@ -176,8 +150,7 @@ public function getUserDashboard() { ON a.id = b.user_id WHERE b.status = 'approved' "); - $verified_members = $verified_members ? count($verified_members) : 0; - $return["verified_members"] = $verified_members; + $return["verified_members"] = $verified_members ? count($verified_members) : 0; // get total members count $total_members = DB::select(" @@ -185,8 +158,7 @@ public function getUserDashboard() { FROM users WHERE role = 'member' "); - $total_members = $total_members ? count($total_members) : 0; - $return["total_members"] = $total_members; + $return["total_members"] = $total_members ? count($total_members) : 0; // find rank $ranking = DB::select(" @@ -395,7 +367,6 @@ function($x, $y) { // remove ranking object. not needed unset($return["ranking"]); - // info($return); return $this->successResponse($return); } @@ -537,7 +508,6 @@ public function getMembershipPage() { $addresses_count = $addresses_count ? $addresses_count : 1; $return["avg_uptime"] = round((float) ($return["avg_uptime"] / $addresses_count), 2); - // info($return); return $this->successResponse($return); } @@ -2277,6 +2247,7 @@ public function getMembers(Request $request) return $this->successResponse($members); + /* $limit = $request->limit ?? 50; $slide_value_uptime = $request->uptime ?? 0; @@ -2449,6 +2420,7 @@ public function getMembers(Request $request) $users = $users->sortByDesc($sort_key)->values(); $users = Helper::paginate($users, $limit, $request->page); return $this->successResponse($users); + */ } public function getMemberDetail($id, Request $request) { From f5f63db3e44e352c8e1b85ecf259c06848f32122 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 26 Oct 2022 11:42:01 -0400 Subject: [PATCH 078/162] shufti declined reason more to verification intake --- app/Http/Controllers/Api/V1/AdminController.php | 17 ++++++++++++----- app/Http/Controllers/Api/V1/UserController.php | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 2a3e8137..1bd667ba 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -1885,16 +1885,13 @@ public function getVerificationUsers(Request $request) $limit = $request->limit ?? 50; $users = User::where('users.role', 'member') ->where('banned', 0) - ->join('profile', function ($query) { - $query->on('profile.user_id', '=', 'users.id') - ->where('profile.status', 'pending'); - }) ->join('shuftipro', 'shuftipro.user_id', '=', 'users.id') + ->where('shuftipro.status', 'denied') + ->orWhere('shuftipro.status', 'pending') ->select([ 'users.id as user_id', 'users.created_at', 'users.email', - 'profile.*', 'shuftipro.status as kyc_status', 'shuftipro.background_checks_result', 'shuftipro.manual_approved_at' @@ -2034,6 +2031,7 @@ public function getVerificationDetail($id) 'users.*', 'shuftipro.status as kyc_status', 'shuftipro.background_checks_result', + 'shuftipro.data' ]) ->where('users.role', 'member') ->where('banned', 0) @@ -2048,6 +2046,15 @@ public function getVerificationDetail($id) $url = Storage::disk('local')->url($user->shuftipro->address_proof); $user->shuftipro->address_proof_link = asset($url); } + + $declined_reason = ''; + + try { + $declined_reason = json_decode(json_decode($user->data))->declined_reason; + } catch (Exception $e) {} + + $user->declined_reason = $declined_reason; + return $this->successResponse($user); } diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 4d74d613..180f0264 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1015,7 +1015,7 @@ public function updateShuftiproStatus() { } else { $events = [ 'verification.accepted', - 'verification.declined', + 'verification.declined' ]; if (isset($data['event']) && in_array($data['event'], $events)) { From b58e43b9af9bae334e7ac493a9f853fa4ca5c2fa Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 26 Oct 2022 12:01:22 -0400 Subject: [PATCH 079/162] enable dev node verifier --- app/Http/Controllers/Api/V1/AuthController.php | 4 ++-- routes/api.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AuthController.php b/app/Http/Controllers/Api/V1/AuthController.php index 1f696e0b..0237b1d6 100644 --- a/app/Http/Controllers/Api/V1/AuthController.php +++ b/app/Http/Controllers/Api/V1/AuthController.php @@ -61,7 +61,7 @@ public function testHash() { exit(Hash::make('ledgerleapllc')); } - /* + public function devVerifyNode($address) { $query = DB::select(" @@ -103,7 +103,7 @@ public function devVerifyNode($address) return $this->metaSuccess(); } - */ + /** * Auth user function diff --git a/routes/api.php b/routes/api.php index 5455f065..a6c7471b 100644 --- a/routes/api.php +++ b/routes/api.php @@ -26,7 +26,7 @@ */ //// REMOVE -// Route::get('/dev-verify-node/{address}', [AuthController::class, 'devVerifyNode'])->where('address', '[0-9a-zA-Z]+'); +Route::get('/dev-verify-node/{address}', [AuthController::class, 'devVerifyNode'])->where('address', '[0-9a-zA-Z]+'); Route::namespace('Api')->middleware([])->group(function () { Route::post('hellosign', [HelloSignController::class, 'hellosignHook']); From 495714308c4d5249ffc944705b1b3f7201c11d43 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 26 Oct 2022 12:32:16 -0400 Subject: [PATCH 080/162] track manual kyc reviewer admin --- app/Http/Controllers/Api/V1/AdminController.php | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 1bd667ba..9108bc0d 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -990,10 +990,11 @@ public function infoDashboard(Request $request) public function bypassApproveKYC($user_id) { - $user_id = (int) $user_id; - $now = Carbon::now('UTC'); + $user_id = (int) $user_id; + $user = User::find($user_id); + $now = Carbon::now('UTC'); + $admin_user = auth()->user(); - $user = User::find($user_id); if ($user && $user->role == 'member') { $user->kyc_verified_at = $now; $user->approve_at = $now; @@ -1001,6 +1002,7 @@ public function bypassApproveKYC($user_id) $user->save(); $profile = Profile::where('user_id', $user_id)->first(); + if (!$profile) { $profile = new Profile; $profile->user_id = $user_id; @@ -1008,17 +1010,21 @@ public function bypassApproveKYC($user_id) $profile->last_name = $user->last_name; $profile->type = $user->type; } + $profile->status = 'approved'; $profile->save(); - $shuftipro = Shuftipro::where('user_id', $user_id)->first(); + if (!$shuftipro) { $shuftipro = new Shuftipro; $shuftipro->user_id = $user_id; $shuftipro->reference_id = 'ByPass#' . time(); } + $shuftipro->is_successful = 1; $shuftipro->status = 'approved'; + $shuftipro->manual_approved_at = $now; + $shuftipro->manual_reviewer = $admin_user->pseudonym; $shuftipro->save(); } From 043780ae014066bacf1d8aa0652087445c8045b3 Mon Sep 17 00:00:00 2001 From: = <=> Date: Wed, 26 Oct 2022 13:00:03 -0400 Subject: [PATCH 081/162] admin bypass track email of admin --- app/Http/Controllers/Api/V1/AdminController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 9108bc0d..63c52edb 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -1024,7 +1024,7 @@ public function bypassApproveKYC($user_id) $shuftipro->is_successful = 1; $shuftipro->status = 'approved'; $shuftipro->manual_approved_at = $now; - $shuftipro->manual_reviewer = $admin_user->pseudonym; + $shuftipro->manual_reviewer = $admin_user->email; $shuftipro->save(); } From 1646cdce74f7ea15fc806969832656222783a0a8 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 26 Oct 2022 20:40:20 -0400 Subject: [PATCH 082/162] # Updates --- app/Console/Helper.php | 16 +++---- .../Controllers/Api/V1/UserController.php | 16 ++----- tests/Feature/UserFunctionsTest.php | 45 +++++++++++++++++++ 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/app/Console/Helper.php b/app/Console/Helper.php index 0c7e40ab..8de79b87 100644 --- a/app/Console/Helper.php +++ b/app/Console/Helper.php @@ -101,20 +101,20 @@ public static function getAccountInfoStandard($user) $state_root_hash = $casper_client->getStateRootHash($block_hash); $curl = curl_init(); - $json_data = array( + $json_data = [ 'id' => (int) time(), 'jsonrpc' => '2.0', 'method' => 'state_get_dictionary_item', - 'params' => array( + 'params' => [ 'state_root_hash' => $state_root_hash, - 'dictionary_identifier' => array( - 'URef' => array( + 'dictionary_identifier' => [ + 'URef' => [ 'seed_uref' => $account_info_urls_uref, 'dictionary_item_key' => $account_hash, - ) - ) - ) - ); + ] + ] + ] + ]; curl_setopt($curl, CURLOPT_URL, $node_ip . '/rpc'); curl_setopt($curl, CURLOPT_POST, true); diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 4d74d613..00dd0e45 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1072,16 +1072,7 @@ public function getProfile() $user = auth()->user()->load(['profile', 'pagePermissions', 'permissions', 'shuftipro', 'shuftiproTemp']); Helper::getAccountInfoStandard($user); $user->metric = Helper::getNodeInfo($user); - - $items = Setting::get(); - $settings = []; - if ($items) { - foreach ($items as $item) { - $settings[$item->name] = $item->value; - } - } - $user->globalSettings = $settings; - + $user->globalSettings = Helper::getSettings(); return $this->successResponse($user); } @@ -2528,9 +2519,8 @@ public function getCaKycHash($hash) ON b.user_id = c.id WHERE a.casper_association_kyc_hash = '$hash' "); - $selection = $selection[0] ?? array(); - - return $this->successResponse($selection); + + return $this->successResponse($selection[0] ?? []); } public function getMyVotes(Request $request) diff --git a/tests/Feature/UserFunctionsTest.php b/tests/Feature/UserFunctionsTest.php index fcc68a05..240115d0 100644 --- a/tests/Feature/UserFunctionsTest.php +++ b/tests/Feature/UserFunctionsTest.php @@ -133,6 +133,39 @@ public function testCheckValidatorAddress() { ]); } + public function testGetUserDashboard() { + $token = $this->getUserToken(); + + $response = $this->withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $token, + ])->json('get', '/api/v1/users/get-dashboard'); + + // $apiResponse = $response->baseResponse->getData(); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'message', + 'data', + ]); + } + + public function testGetMembershipPage() { + + } + + public function testGetNodesPage() { + + } + + public function testGetMyEras() { + + } + + public function testCanVote() { + + } + public function testChangeEmail() { $token = $this->getUserToken(); @@ -171,6 +204,10 @@ public function testGetProfile() { ]); } + public function testSendHellosignRequest() { + + } + public function testSubmitPublicAddress() { $token = $this->getUserToken(); @@ -283,6 +320,7 @@ public function testSubmitKYC() { ]); } + /* public function testVerifyOwnerNode() { $token = $this->getUserToken(); @@ -298,7 +336,9 @@ public function testVerifyOwnerNode() { 'data', ]); } + */ + /* public function testGetOwnerNodes() { $token = $this->getUserToken(); @@ -314,7 +354,9 @@ public function testGetOwnerNodes() { 'data', ]); } + */ + /* public function testResendInviteOwner() { $token = $this->getUserToken(); @@ -334,6 +376,7 @@ public function testResendInviteOwner() { 'data', ]); } + */ public function testGetMessageContent() { $token = $this->getUserToken(); @@ -368,6 +411,7 @@ public function testSaveShuftiproTemp() { ]); } + /* public function testUpdateShuftiproTemp() { $token = $this->getUserToken(); @@ -387,6 +431,7 @@ public function testUpdateShuftiproTemp() { 'data', ]); } + */ public function testDeleteShuftiproTemp() { $token = $this->getUserToken(); From 4dff78463209d0083ba26ebcc4183581a9093c08 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 26 Oct 2022 22:45:56 -0400 Subject: [PATCH 083/162] # Unit Tests --- app/Console/Commands/NodeInfo.php | 4 +- app/Console/Helper.php | 3 +- .../Controllers/Api/V1/AdminController.php | 3 +- .../Controllers/Api/V1/UserController.php | 536 +++++++----------- app/Services/NodeHelper.php | 48 +- tests/Feature/UserFunctionsTest.php | 39 ++ 6 files changed, 272 insertions(+), 361 deletions(-) diff --git a/app/Console/Commands/NodeInfo.php b/app/Console/Commands/NodeInfo.php index 566441da..4e01ffd6 100644 --- a/app/Console/Commands/NodeInfo.php +++ b/app/Console/Commands/NodeInfo.php @@ -35,7 +35,7 @@ public function __construct() public function handle() { - $nodeHelper = new NodeHelper(); - $nodeHelper->getValidatorStanding(); + // $nodeHelper = new NodeHelper(); + // $nodeHelper->getValidatorStanding(); } } \ No newline at end of file diff --git a/app/Console/Helper.php b/app/Console/Helper.php index 8de79b87..9878962e 100644 --- a/app/Console/Helper.php +++ b/app/Console/Helper.php @@ -84,7 +84,6 @@ public static function publicKeyToAccountHash($public_key) public static function getAccountInfoStandard($user) { $vid = strtolower($user->public_address_node ?? ''); - if (!$vid) return; // convert to account hash @@ -206,6 +205,7 @@ public static function getTokenPrice() return $response->json(); } + /* public static function getNodeInfo($user, $public_address_node = null) { if (!$public_address_node) $public_address_node = $user->public_address_node; @@ -301,6 +301,7 @@ public static function getNodeInfo($user, $public_address_node = null) $metric['monitoring_criteria'] = $monitoringCriteria; return $metric; } + */ public static function paginate($items, $perPage = 5, $page = null, $options = []) { diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 63c52edb..d05d0161 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -680,7 +680,6 @@ public function getUsers(Request $request) if ($user->profile_status == 'approved') { $status = 'Verified'; - if ($user->extra_status) { $status = $user->extra_status; } @@ -727,7 +726,7 @@ public function getUserDetail($id) } $user->membership_status = $status; - $user->metric = Helper::getNodeInfo($user); + // $user->metric = Helper::getNodeInfo($user); $addresses = $user->addresses ?? []; $current_era_id = Helper::getCurrentERAId(); diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index d04a562d..276f83d7 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -86,10 +86,10 @@ public function __construct( ProfileRepository $profileRepo, OwnerNodeRepository $ownerNodeRepo ) { - $this->userRepo = $userRepo; - $this->verifyUserRepo = $verifyUserRepo; - $this->profileRepo = $profileRepo; - $this->ownerNodeRepo = $ownerNodeRepo; + $this->userRepo = $userRepo; + $this->verifyUserRepo = $verifyUserRepo; + $this->profileRepo = $profileRepo; + $this->ownerNodeRepo = $ownerNodeRepo; $this->failed_verification_response = 'Failed verification'; } @@ -101,21 +101,21 @@ public function getUserDashboard() { // Define complete return object $return = [ - "node_rank" => 0, - "node_rank_total" => 100, - "total_stake" => 0, - "total_self_stake" => 0, - "total_delegators" => 0, - "uptime" => 0, - "eras_active" => 0, - "eras_since_bad_mark" => $current_era_id, - "total_bad_marks" => 0, - "update_responsiveness" => 100, - "peers" => 0, - "total_members" => 0, - "verified_members" => 0, - "association_members" => [], - "ranking" => [] + "node_rank" => 0, + "node_rank_total" => 100, + "total_stake" => 0, + "total_self_stake" => 0, + "total_delegators" => 0, + "uptime" => 0, + "eras_active" => 0, + "eras_since_bad_mark" => $current_era_id, + "total_bad_marks" => 0, + "update_responsiveness" => 100, + "peers" => 0, + "total_members" => 0, + "verified_members" => 0, + "association_members" => [], + "ranking" => [] ]; // get all active members @@ -133,14 +133,7 @@ public function getUserDashboard() { ON c.id = d.user_id WHERE a.era_id = $current_era_id and d.status = 'approved' "); - - if (!$association_members) { - $association_members = []; - } - - foreach ($association_members as $member) { - $return["association_members"][] = $member; - } + $return["association_members"] = $association_members ?? []; // get verified members count $verified_members = DB::select(" @@ -168,76 +161,53 @@ public function getUserDashboard() { bid_delegation_rate, bid_total_staked_amount FROM all_node_data2 - WHERE era_id = $current_era_id + WHERE era_id = $current_era_id AND in_current_era = 1 - AND in_next_era = 1 - AND in_auction = 1 + AND in_next_era = 1 + AND in_auction = 1 "); - $max_delegators = 0; - $max_stake_amount = 0; + $max_delegators = 0; + $max_stake_amount = 0; foreach ($ranking as $r) { - if ((int)$r->bid_delegators_count > $max_delegators) { - $max_delegators = (int)$r->bid_delegators_count; + if ((int) $r->bid_delegators_count > $max_delegators) { + $max_delegators = (int) $r->bid_delegators_count; } - - if ((int)$r->bid_total_staked_amount > $max_stake_amount) { - $max_stake_amount = (int)$r->bid_total_staked_amount; + if ((int) $r->bid_total_staked_amount > $max_stake_amount) { + $max_stake_amount = (int) $r->bid_total_staked_amount; } } foreach ($ranking as $r) { - $uptime_score = ( - 25 * (float)$r->uptime - ) / 100; - $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; + $uptime_score = (float) (25 * (float) $r->uptime / 100); + $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; - $fee_score = ( - 25 * - (1 - ((float)$r->bid_delegation_rate / 100)) - ); - $fee_score = $fee_score < 0 ? 0 : $fee_score; + $fee_score = 25 * (1 - (float) ((float) $r->bid_delegation_rate / 100)); + $fee_score = $fee_score < 0 ? 0 : $fee_score; - $count_score = ( - (float)$r->bid_delegators_count / - $max_delegators - ) * 25; - $count_score = $count_score < 0 ? 0 : $count_score; + $count_score = (float) ((float) $r->bid_delegators_count / $max_delegators) * 25; + $count_score = $count_score < 0 ? 0 : $count_score; - $stake_score = ( - (float)$r->bid_total_staked_amount / - $max_stake_amount - ) * 25; - $stake_score = $stake_score < 0 ? 0 : $stake_score; + $stake_score = (float) ((float) $r->bid_total_staked_amount / $max_stake_amount) * 25; + $stake_score = $stake_score < 0 ? 0 : $stake_score; - $return["ranking"][$r->public_key] = ( - $uptime_score + - $fee_score + - $count_score + - $stake_score - ); + $return["ranking"][$r->public_key] = $uptime_score + $fee_score + $count_score + $stake_score; } - uasort( - $return["ranking"], - function($x, $y) { - if ($x == $y) { - return 0; - } - - return ($x > $y) ? -1 : 1; + uasort($return["ranking"], function($x, $y) { + if ($x == $y) { + return 0; } - ); + return ($x > $y) ? -1 : 1; + }); - $sorted_ranking = array(); + $sorted_ranking = []; $i = 1; - foreach ($return["ranking"] as $public_key => $score) { $sorted_ranking[$public_key] = $i; $i += 1; } - - $return["ranking"] = $sorted_ranking; + $return["ranking"] = $sorted_ranking; $return["node_rank_total"] = count($sorted_ranking); // parse node addresses @@ -257,32 +227,18 @@ function($x, $y) { AND b.user_id = $user_id "); - if (!$addresses) { - $addresses = array(); - } - - // get settings - $voting_eras_to_vote = DB::select(" - SELECT value - FROM settings - WHERE name = 'voting_eras_to_vote' - "); - $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); - $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); + if (!$addresses) $addresses = []; - $uptime_calc_size = DB::select(" - SELECT value - FROM settings - WHERE name = 'uptime_calc_size' - "); - $uptime_calc_size = $uptime_calc_size[0] ?? array(); - $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); + $settings = Helper::getSettings(); + + $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 0; + $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 0; // for each address belonging to a user foreach ($addresses as $address) { $p = $address->public_key ?? ''; - $total_bad_marks = DB::select(" + $temp = DB::select(" SELECT era_id FROM all_node_data2 WHERE public_key = '$p' @@ -293,108 +249,98 @@ function($x, $y) { ORDER BY era_id DESC "); - $eras_since_bad_mark = $total_bad_marks[0] ?? array(); - $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); - $total_bad_marks = count((array)$total_bad_marks); - + $total_bad_marks = 0; + $eras_since_bad_mark = $current_era_id; + if ($temp && isset($temp[0])) { + $total_bad_marks = count($temp); + $eras_since_bad_mark = $current_era_id - (int) ($temp[0]->era_id ?? 0); + } if ($eras_since_bad_mark < $return["eras_since_bad_mark"]) { $return["eras_since_bad_mark"] = $eras_since_bad_mark; } - $eras_active = DB::select(" + $temp = DB::select(" SELECT era_id FROM all_node_data2 WHERE public_key = '$p' ORDER BY era_id ASC LIMIT 1 "); - - $eras_active = (int)($eras_active[0]->era_id ?? 0); - + $eras_active = 0; + if ($temp && isset($temp[0])) { + $eras_active = (int) ($temp[0]->era_id ?? 0); + } if ($current_era_id - $eras_active > $return["eras_active"]) { $return["eras_active"] = $current_era_id - $eras_active; } // Calculate historical_performance from past $uptime_calc_size eras $missed = 0; - $in_current_eras = DB::select(" + $temp = DB::select(" SELECT in_current_era FROM all_node_data2 WHERE public_key = '$p' ORDER BY era_id DESC LIMIT $uptime_calc_size "); + if (!$temp) $temp = []; - $in_current_eras = $in_current_eras ? $in_current_eras : array(); - $window = $in_current_eras ? count($in_current_eras) : 0; - - foreach ($in_current_eras as $c) { - $in = (bool)($c->in_current_era ?? 0); - + $window = count($temp); + foreach ($temp as $c) { + $in = (bool) ($c->in_current_era ?? 0); if (!$in) { $missed += 1; } } - $uptime = (float)($address->uptime ?? 0); - $historical_performance = round( - ($uptime * ($window - $missed)) / $window, - 2, - PHP_ROUND_HALF_UP - ); + $uptime = (float) ($address->uptime ?? 0); + $historical_performance = round((float) ($uptime * ($window - $missed) / $window), 2); if ( - array_key_exists($p, $return["ranking"]) && ( - $return["node_rank"] == 0 || - $return["ranking"][$p] < $return["node_rank"] - ) + array_key_exists($p, $return["ranking"]) && + ($return["node_rank"] == 0 || $return["ranking"][$p] < $return["node_rank"]) ) { $return["node_rank"] = $return["ranking"][$p]; } - $return["total_bad_marks"] += $total_bad_marks; - $return["total_stake"] += (int)($address->bid_total_staked_amount ?? 0); - $return["total_self_stake"] += (int)($address->bid_self_staked_amount ?? 0); - $return["total_delegators"] += (int)($address->delegators ?? 0); - $return["peers"] += (int)($address->peers ?? 0); - $return["uptime"] += $historical_performance; + $return["total_bad_marks"] += $total_bad_marks; + $return["total_stake"] += (int) ($address->bid_total_staked_amount ?? 0); + $return["total_self_stake"] += (int) ($address->bid_self_staked_amount ?? 0); + $return["total_delegators"] += (int) ($address->delegators ?? 0); + $return["peers"] += (int) ($address->peers ?? 0); + $return["uptime"] += $historical_performance; } - $addresses_count = count($addresses); - $addresses_count = $addresses_count ? $addresses_count : 1; - $return["uptime"] = round($return["uptime"] / $addresses_count, 2); + $addresses_count = count($addresses); + $addresses_count = $addresses_count ? $addresses_count : 1; + $return["uptime"] = round((float) ($return["uptime"] / $addresses_count), 2); - // remove ranking object. not needed unset($return["ranking"]); return $this->successResponse($return); } public function getMembershipPage() { - $user = auth()->user(); + $user = auth()->user()->load(['profile']); $user_id = $user->id ?? 0; - // Get current era - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + $current_era_id = Helper::getCurrentERAId(); + $settings = Helper::getSettings(); - // Define complete return object - $return = array( - "node_status" => "Offline", - "kyc_status" => "Not Verified", - "uptime" => array(), - "avg_uptime" => 0, - "total_eras" => 0, - "eras_since_bad_mark" => $current_era_id, - "total_bad_marks" => 0, + $return = [ + "node_status" => $user->node_status ?? '', + "kyc_status" => "Not Verified", + "uptime" => [], + "avg_uptime" => 0, + "total_eras" => 0, + "eras_since_bad_mark" => $current_era_id, + "total_bad_marks" => 0, "update_responsiveness" => 100, - "peers" => 0 - ); + "peers" => 0 + ]; + if (isset($user->profile) && $user->profile->status == 'approved') { + $return['kyc_status'] = 'Verified'; + } $addresses = DB::select(" SELECT @@ -410,30 +356,14 @@ public function getMembershipPage() { WHERE a.era_id = $current_era_id AND b.user_id = $user_id "); + if (!$addresses) $addresses = []; - if (!$addresses) { - $addresses = array(); - } - - if ( - isset($addresses[0]) && - $addresses[0]->kyc_status - ) { - $return["kyc_status"] = "Verified"; - } - - $uptime_calc_size = DB::select(" - SELECT value - FROM settings - WHERE name = 'uptime_calc_size' - "); - $uptime_calc_size = $uptime_calc_size[0] ?? array(); - $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); + $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) ($settings['uptime_calc_size']) : 0; foreach ($addresses as $address) { $p = $address->public_key ?? ''; - $total_bad_marks = DB::select(" + $temp = DB::select(" SELECT era_id FROM all_node_data2 WHERE public_key = '$p' @@ -443,117 +373,83 @@ public function getMembershipPage() { ) ORDER BY era_id DESC "); + if (!$temp) $temp = []; - $eras_since_bad_mark = $total_bad_marks[0] ?? array(); - $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); - $total_bad_marks = count((array)$total_bad_marks); - + $eras_since_bad_mark = $current_era_id; + if ($temp && isset($temp[0])) { + $eras_since_bad_mark = $current_era_id - (int) ($temp[0]->era_id ?? 0); + } + $total_bad_marks = count($temp); if ($eras_since_bad_mark < $return["eras_since_bad_mark"]) { $return["eras_since_bad_mark"] = $eras_since_bad_mark; } - $total_eras = DB::select(" + $temp = DB::select(" SELECT era_id FROM all_node_data2 WHERE public_key = '$p' ORDER BY era_id ASC LIMIT 1 "); - - $total_eras = (int)($total_eras[0]->era_id ?? 0); - + if (!$temp) $temp = []; + $total_eras = 0; + if ($temp && isset($temp[0])) { + $total_eras = (int) ($temp[0]->era_id ?? 0); + } if ($current_era_id - $total_eras > $return["total_eras"]) { $return["total_eras"] = $current_era_id - $total_eras; } // Calculate historical_performance from past $uptime_calc_size eras $missed = 0; - $in_current_eras = DB::select(" + $temp = DB::select(" SELECT in_current_era FROM all_node_data2 WHERE public_key = '$p' ORDER BY era_id DESC LIMIT $uptime_calc_size "); + if (!$temp) $temp = []; - $in_current_eras = $in_current_eras ? $in_current_eras : array(); - $window = $in_current_eras ? count($in_current_eras) : 0; - - foreach ($in_current_eras as $c) { - $in = (bool)($c->in_current_era ?? 0); + $window = count($temp); + foreach ($temp as $c) { + $in = (bool) ($c->in_current_era ?? 0); if (!$in) { $missed += 1; } } - $uptime = (float)($address->uptime ?? 0); - $historical_performance = round( - ($uptime * ($window - $missed)) / $window, - 2, - PHP_ROUND_HALF_UP - ); - - $return["total_bad_marks"] += $total_bad_marks; - $return["peers"] += (int)($address->peers ?? 0); - $return["uptime"][$p] = (float)($address->uptime ?? 0); - $return["avg_uptime"] += (float)($address->uptime ?? 0); + $uptime = (float) ($address->uptime ?? 0); + $historical_performance = round((float) ($uptime * ($window - $missed) / $window), 2); - if ((int)$address->bid_inactive == 0) { - $return["node_status"] = "Online"; - } + $return["total_bad_marks"] += $total_bad_marks; + $return["peers"] += (int) ($address->peers ?? 0); + $return["uptime"][$p] = $historical_performance; + $return["avg_uptime"] += $historical_performance; } - $addresses_count = count($addresses); - $addresses_count = $addresses_count ? $addresses_count : 1; + $addresses_count = count($addresses); + $addresses_count = $addresses_count ? $addresses_count : 1; $return["avg_uptime"] = round((float) ($return["avg_uptime"] / $addresses_count), 2); return $this->successResponse($return); } public function getNodesPage() { - $user = auth()->user(); + $user = auth()->user(); $user_id = $user->id ?? 0; - // Get current era - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + $nodeHelper = new NodeHelper(); + $current_era_id = Helper::getCurrentERAId(); + $settings = Helper::getSettings(); // Define complete return object - $return = array( + $return = [ "mbs" => 0, - "ranking" => array( - "0123456789abcdef" => 0 - ), - "addresses" => array( - "0123456789abcdef" => array( - "stake_amount" => 0, - "delegators" => 0, - "uptime" => 0, - "update_responsiveness" => 100, - "peers" => 0, - "daily_earning" => 0, - "total_eras" => 0, - "eras_since_bad_mark" => $current_era_id, - "total_bad_marks" => 0, - "validator_rewards" => array( - "day" => array(), - "week" => array(), - "month" => array(), - "year" => array() - ) - ) - ) - ); - - unset($return["ranking"]["0123456789abcdef"]); - unset($return["addresses"]["0123456789abcdef"]); - $nodeHelper = new NodeHelper(); + "ranking" => [], + "addresses" => [] + ]; // get ranking $ranking = DB::select(" @@ -568,70 +464,48 @@ public function getNodesPage() { AND in_next_era = 1 AND in_auction = 1 "); + if (!$ranking) $ranking = []; + $max_delegators = 0; $max_stake_amount = 0; - foreach ($ranking as $r) { - if ((int)$r->bid_delegators_count > $max_delegators) { - $max_delegators = (int)$r->bid_delegators_count; + if ((int) $r->bid_delegators_count > $max_delegators) { + $max_delegators = (int)$r->bid_delegators_count; } - - if ((int)$r->bid_total_staked_amount > $max_stake_amount) { + if ((int) $r->bid_total_staked_amount > $max_stake_amount) { $max_stake_amount = (int)$r->bid_total_staked_amount; } } foreach ($ranking as $r) { - $uptime_score = ( - 25 * (float)$r->uptime - ) / 100; - $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; + $uptime_score = (float) (25 * (float) $r->uptime / 100); + $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; - $fee_score = ( - 25 * - (1 - ((float)$r->bid_delegation_rate / 100)) - ); - $fee_score = $fee_score < 0 ? 0 : $fee_score; + $fee_score = 25 * (1 - (float) ($r->bid_delegation_rate / 100)); + $fee_score = $fee_score < 0 ? 0 : $fee_score; - $count_score = ( - (float)$r->bid_delegators_count / - $max_delegators - ) * 25; - $count_score = $count_score < 0 ? 0 : $count_score; + $count_score = (float) ($r->bid_delegators_count / $max_delegators) * 25; + $count_score = $count_score < 0 ? 0 : $count_score; - $stake_score = ( - (float)$r->bid_total_staked_amount / - $max_stake_amount - ) * 25; - $stake_score = $stake_score < 0 ? 0 : $stake_score; + $stake_score = (float) ($r->bid_total_staked_amount / $max_stake_amount) * 25; + $stake_score = $stake_score < 0 ? 0 : $stake_score; - $return["ranking"][$r->public_key] = ( - $uptime_score + - $fee_score + - $count_score + - $stake_score - ); + $return["ranking"][$r->public_key] = $uptime_score + $fee_score + $count_score + $stake_score; } - uasort( - $return["ranking"], - function($x, $y) { - if ($x == $y) { - return 0; - } - - return ($x > $y) ? -1 : 1; + uasort($return["ranking"], function($x, $y) { + if ($x == $y) { + return 0; } - ); + return ($x > $y) ? -1 : 1; + }); - $sorted_ranking = array(); + $sorted_ranking = []; $i = 1; - foreach ($return["ranking"] as $public_key => $score) { $sorted_ranking[$public_key] = $i; $i += 1; } - $return["ranking"] = $sorted_ranking; $addresses = DB::select(" @@ -648,24 +522,15 @@ function($x, $y) { WHERE a.era_id = $current_era_id AND b.user_id = $user_id "); + if (!$addresses) $addresses = []; - if (!$addresses) { - $addresses = array(); - } - - $uptime_calc_size = DB::select(" - SELECT value - FROM settings - WHERE name = 'uptime_calc_size' - "); - $uptime_calc_size = $uptime_calc_size[0] ?? array(); - $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); + $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 0; // for each member's node address foreach ($addresses as $address) { $p = $address->public_key ?? ''; - $total_bad_marks = DB::select(" + $temp = DB::select(" SELECT era_id FROM all_node_data2 WHERE public_key = '$p' @@ -675,52 +540,53 @@ function($x, $y) { ) ORDER BY era_id DESC "); + if (!$temp) $temp = []; - $eras_since_bad_mark = $total_bad_marks[0] ?? array(); - $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); - $total_bad_marks = count((array)$total_bad_marks); + $eras_since_bad_mark = $current_era_id; + if (isset($temp[0])) { + $eras_since_bad_mark = $current_era_id - (int) ($temp[0]->era_id ?? 0); + } + $total_bad_marks = count($temp); - $total_eras = DB::select(" + $temp = DB::select(" SELECT era_id FROM all_node_data2 WHERE public_key = '$p' ORDER BY era_id ASC LIMIT 1 "); - - $total_eras = (int)($total_eras[0]->era_id ?? 0); + if (!$temp) $temp = []; + $total_eras = 0; + if (isset($temp[0])) { + $total_eras = (int) ($temp[0]->era_id ?? 0); + if ($current_era_id > $total_eras) $total_eras = $current_era_id - $total_eras; + } // Calculate historical_performance from past $uptime_calc_size eras $missed = 0; - $in_current_eras = DB::select(" + $temp = DB::select(" SELECT in_current_era FROM all_node_data2 WHERE public_key = '$p' ORDER BY era_id DESC LIMIT $uptime_calc_size "); + if (!$temp) $temp = []; - $in_current_eras = $in_current_eras ? $in_current_eras : array(); - $window = $in_current_eras ? count($in_current_eras) : 0; - - foreach ($in_current_eras as $c) { - $in = (bool)($c->in_current_era ?? 0); + $window = count($temp); + foreach ($temp as $c) { + $in = (bool) ($c->in_current_era ?? 0); if (!$in) { $missed += 1; } } - $uptime = (float)($address->uptime ?? 0); - $historical_performance = round( - ($uptime * ($window - $missed)) / $window, - 2, - PHP_ROUND_HALF_UP - ); + $uptime = (float) ($address->uptime ?? 0); + $historical_performance = round((float) ($uptime * ($window - $missed) / $window), 2); - // Calc earning - $one_day_ago = Carbon::now('UTC')->subHours(24); - $daily_earning = DB::select(" + $one_day_ago = Carbon::now('UTC')->subHours(24); + $temp = DB::select(" SELECT bid_self_staked_amount FROM all_node_data2 WHERE public_key = '$p' @@ -728,8 +594,10 @@ function($x, $y) { ORDER BY era_id DESC LIMIT 1 "); - $daily_earning = $daily_earning[0]->bid_self_staked_amount ?? 0; - $daily_earning = $address->bid_self_staked_amount - $daily_earning; + if (!$temp) $temp = []; + $daily_earning = 0; + if (isset($temp[0])) $daily_earning = (float) ($temp[0]->bid_self_staked_amount ?? 0); + $daily_earning = (float) $address->bid_self_staked_amount - $daily_earning; $daily_earning = $daily_earning < 0 ? 0 : $daily_earning; $earning_day = $nodeHelper->getValidatorRewards($p, 'day'); @@ -737,51 +605,45 @@ function($x, $y) { $earning_month = $nodeHelper->getValidatorRewards($p, 'month'); $earning_year = $nodeHelper->getValidatorRewards($p, 'year'); - $return["addresses"][$p] = array( + $return["addresses"][$p] = [ "stake_amount" => $address->bid_total_staked_amount, "delegators" => $address->bid_delegators_count, "uptime" => $historical_performance, "update_responsiveness" => 100, - "peers" => (int)($address->peers), + "peers" => (int) ($address->peers ?? 0), "daily_earning" => $daily_earning, - "total_eras" => $current_era_id - $total_eras, + "total_eras" => $total_eras, "eras_since_bad_mark" => $eras_since_bad_mark, "total_bad_marks" => $total_bad_marks, - "validator_rewards" => array( - "day" => $earning_day, - "week" => $earning_week, - "month" => $earning_month, - "year" => $earning_year - ) - ); + "validator_rewards" => [ + "day" => $earning_day, + "week" => $earning_week, + "month" => $earning_month, + "year" => $earning_year + ] + ]; } // get mbs - $mbs = DB::select(" + $temp = DB::select(" SELECT mbs FROM mbs ORDER BY era_id DESC LIMIT 1 "); - $return["mbs"] = (int)($mbs[0]->mbs ?? 0); + if (!$temp) $temp = []; + $return['mbs'] = 0; + if (isset($temp[0])) $return['mbs'] = (int) ($temp[0]->mbs ?? 0); - // info($return); return $this->successResponse($return); } public function getMyEras() { - $user = auth()->user(); + $user = auth()->user(); $user_id = $user->id ?? 0; - // Get current era - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); - + $current_era_id = Helper::getCurrentERAId(); + // define return object $return = array( "addresses" => array( @@ -1071,7 +933,7 @@ public function getProfile() { $user = auth()->user()->load(['profile', 'pagePermissions', 'permissions', 'shuftipro', 'shuftiproTemp']); Helper::getAccountInfoStandard($user); - $user->metric = Helper::getNodeInfo($user); + // $user->metric = Helper::getNodeInfo($user); $user->globalSettings = Helper::getSettings(); return $this->successResponse($user); } diff --git a/app/Services/NodeHelper.php b/app/Services/NodeHelper.php index f5714b38..60bcde1d 100644 --- a/app/Services/NodeHelper.php +++ b/app/Services/NodeHelper.php @@ -36,6 +36,7 @@ public function __construct() // do nothing } + /* public function decodePeers($__peers) { $decoded_peers = array(); @@ -52,7 +53,9 @@ public function decodePeers($__peers) } return $decoded_peers; } + */ + /* public function retrieveGlobalUptime($this_era_id) { $total_data = array(); @@ -114,7 +117,9 @@ public function retrieveGlobalUptime($this_era_id) return $total_data; } + */ + /* public function discoverPeers() { $port8888_responses = array(); @@ -278,6 +283,7 @@ public function discoverPeers() return $port8888_responses; } + */ public function getValidAddresses() { @@ -335,6 +341,7 @@ public function getValidAddresses() return $addresses; } + /* public function getValidatorStanding() { // get node ips from peers @@ -811,7 +818,9 @@ public function getValidatorStanding() return true; } + */ + /* public function getTotalRewards($validatorId) { $response = Http::withOptions([ @@ -819,17 +828,18 @@ public function getTotalRewards($validatorId) ])->get("https://api.CSPR.live/validators/$validatorId/total-rewards"); return $response->json(); } + */ public function getValidatorRewards($validatorId, $_range) { - $nowtime = (int)time(); - $range = 0; - $days = 30; - $leap_year = (int)date('Y'); + $nowtime = (int) time(); + $range = 0; + $days = 30; + $leap_year = (int) date('Y'); $leap_days = $leap_year % 4 == 0 ? 29 : 28; - $month = (int)date('m'); + $month = (int) date('m'); - switch($month) { + switch ($month) { case 1: $days = 31; break; @@ -870,38 +880,38 @@ public function getValidatorRewards($validatorId, $_range) break; } - switch($_range) { + switch ($_range) { case 'day': $range = $nowtime - 86400; - break; + break; case 'week': $range = $nowtime - (86400 * 7); - break; + break; case 'month': $range = $nowtime - (86400 * $days); - break; + break; case 'year': $range = $nowtime - (86400 * (365 + ($leap_days % 2))); - break; + break; default: return false; - break; + break; } - $timestamp = Carbon::createFromTimestamp($range, 'UTC')->toDateTimeString(); + $timestamp = Carbon::createFromTimestamp($range, 'UTC')->toDateTimeString(); $total_records = DailyEarning::where('node_address', $validatorId) ->where('created_at', '>', $timestamp) ->get(); - $new_array = array(); + $new_array = []; $display_record_count = 100; if ($total_records) { - $modded = count($total_records) % $display_record_count; + $modded = count($total_records) % $display_record_count; $numerator = count($total_records) - $modded; - $modulo = $numerator / $display_record_count; - $new_array = array(); - $i = $modulo; + $modulo = $numerator / $display_record_count; + $new_array = []; + $i = $modulo; if ($modulo == 0) { $modulo = 1; @@ -909,7 +919,7 @@ public function getValidatorRewards($validatorId, $_range) foreach ($total_records as $record) { if ($i % $modulo == 0) { - $new_array[(string)strtotime($record->created_at.' UTC')] = (string)$record->self_staked_amount; + $new_array[(string) strtotime($record->created_at.' UTC')] = (string) $record->self_staked_amount; } $i++; } diff --git a/tests/Feature/UserFunctionsTest.php b/tests/Feature/UserFunctionsTest.php index 240115d0..45b180dd 100644 --- a/tests/Feature/UserFunctionsTest.php +++ b/tests/Feature/UserFunctionsTest.php @@ -151,15 +151,54 @@ public function testGetUserDashboard() { } public function testGetMembershipPage() { + $token = $this->getUserToken(); + + $response = $this->withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $token, + ])->json('get', '/api/v1/users/get-membership-page'); + // $apiResponse = $response->baseResponse->getData(); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'message', + 'data', + ]); } public function testGetNodesPage() { + $token = $this->getUserToken(); + $response = $this->withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $token, + ])->json('get', '/api/v1/users/get-nodes-page'); + + // $apiResponse = $response->baseResponse->getData(); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'message', + 'data', + ]); } public function testGetMyEras() { + $token = $this->getUserToken(); + $response = $this->withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $token, + ])->json('get', '/api/v1/users/get-my-eras'); + + // $apiResponse = $response->baseResponse->getData(); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'message', + 'data', + ]); } public function testCanVote() { From b348ee02469e03cb9120eb0c2c2a7ef8328a2434 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 26 Oct 2022 23:11:21 -0400 Subject: [PATCH 084/162] # Unit Test --- .../Controllers/Api/V1/UserController.php | 211 ++++++------------ tests/Feature/UserFunctionsTest.php | 21 +- 2 files changed, 86 insertions(+), 146 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 276f83d7..3b584391 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -556,11 +556,10 @@ public function getNodesPage() { LIMIT 1 "); if (!$temp) $temp = []; + $total_eras = 0; - if (isset($temp[0])) { - $total_eras = (int) ($temp[0]->era_id ?? 0); - if ($current_era_id > $total_eras) $total_eras = $current_era_id - $total_eras; - } + if (isset($temp[0])) $total_eras = (int) ($temp[0]->era_id ?? 0); + if ($current_era_id > $total_eras) $total_eras = $current_era_id - $total_eras; // Calculate historical_performance from past $uptime_calc_size eras $missed = 0; @@ -643,33 +642,14 @@ public function getMyEras() { $user_id = $user->id ?? 0; $current_era_id = Helper::getCurrentERAId(); - - // define return object - $return = array( - "addresses" => array( - "0123456789abcdef" => array( - "uptime" => 0, - "eras_active" => 0, - "eras_since_bad_mark" => $current_era_id, - "total_bad_marks" => 0 - ) - ), - "column_count" => 2, - "eras" => array( - array( - "era_start_time" => '', - "addresses" => array( - "0123456789abcdef" => array( - "in_pool" => false, - "rewards" => 0 - ) - ) - ) - ) - ); + $settings = Helper::getSettings(); - unset($return["addresses"]["0123456789abcdef"]); - unset($return["eras"][0]); + // define return object + $return = [ + "addresses" => [], + "column_count" => 2, + "eras" => [] + ]; // get addresses data $addresses = DB::select(" @@ -683,33 +663,17 @@ public function getMyEras() { WHERE a.era_id = $current_era_id AND b.user_id = $user_id "); - - if (!$addresses) { - $addresses = array(); - } + if (!$addresses) $addresses = []; // get settings - $voting_eras_to_vote = DB::select(" - SELECT value - FROM settings - WHERE name = 'voting_eras_to_vote' - "); - $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); - $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); - - $uptime_calc_size = DB::select(" - SELECT value - FROM settings - WHERE name = 'uptime_calc_size' - "); - $uptime_calc_size = $uptime_calc_size[0] ?? array(); - $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); + $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 0; + $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 0; // for each member's node address foreach ($addresses as $address) { $p = $address->public_key ?? ''; - $total_bad_marks = DB::select(" + $temp = DB::select(" SELECT era_id FROM all_node_data2 WHERE public_key = '$p' @@ -719,60 +683,60 @@ public function getMyEras() { ) ORDER BY era_id DESC "); + if (!$temp) $temp = []; - $eras_since_bad_mark = $total_bad_marks[0] ?? array(); - $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); - $total_bad_marks = count((array)$total_bad_marks); + $eras_since_bad_mark = $current_era_id; + if (isset($temp[0])) { + $eras_since_bad_mark = $current_era_id - (int) ($temp[0]->era_id ?? 0); + } + $total_bad_marks = count($temp); - $eras_active = DB::select(" + $temp = DB::select(" SELECT era_id FROM all_node_data2 WHERE public_key = '$p' ORDER BY era_id ASC LIMIT 1 "); + if (!$temp) $temp = []; - $eras_active = (int)($eras_active[0]->era_id ?? 0); + $eras_active = 0; + if (isset($temp[0])) $eras_active = (int) ($temp[0]->era_id ?? 0); + if ($eras_active < $current_era_id) $eras_active = $current_era_id - $eras_active; // Calculate historical_performance from past $uptime_calc_size eras $missed = 0; - $in_current_eras = DB::select(" + $temp = DB::select(" SELECT in_current_era FROM all_node_data2 WHERE public_key = '$p' ORDER BY era_id DESC LIMIT $uptime_calc_size "); + if (!$temp) $temp = []; - $in_current_eras = $in_current_eras ? $in_current_eras : array(); - $window = $in_current_eras ? count($in_current_eras) : 0; - - foreach ($in_current_eras as $c) { - $in = (bool)($c->in_current_era ?? 0); + $window = count($temp); + foreach ($temp as $c) { + $in = (bool) ($c->in_current_era ?? 0); if (!$in) { $missed += 1; } } - $uptime = (float)($address->uptime ?? 0); - $historical_performance = round( - ($uptime * ($window - $missed)) / $window, - 2, - PHP_ROUND_HALF_UP - ); + $uptime = (float) ($address->uptime ?? 0); + $historical_performance = round((float) ($uptime * ($window - $missed) / $window), 2); - $return["addresses"][$p] = array( - "uptime" => round($historical_performance, 2), - "eras_active" => $current_era_id - $eras_active, + $return["addresses"][$p] = [ + "uptime" => round($historical_performance, 2), + "eras_active" => $eras_active, "eras_since_bad_mark" => $eras_since_bad_mark, - "total_bad_marks" => $total_bad_marks - ); + "total_bad_marks" => $total_bad_marks + ]; } // get eras table data $era_minus_360 = $current_era_id - $uptime_calc_size; - if ($era_minus_360 < 1) { $era_minus_360 = 1; } @@ -791,41 +755,34 @@ public function getMyEras() { AND era_id > $era_minus_360 ORDER BY a.era_id DESC "); + if (!$eras) $eras = []; - if (!$eras) { - $eras = array(); - } - - $sorted_eras = array(); + $sorted_eras = []; // for each node address's era foreach ($eras as $era) { - $era_id = $era->era_id ?? 0; - $era_start_time = $era->created_at ?? ''; - $public_key = $era->public_key; + $era_id = $era->era_id ?? 0; + $era_start_time = $era->created_at ?? ''; + $public_key = $era->public_key; if (!isset($sorted_eras[$era_id])) { - $sorted_eras[$era_id] = array( + $sorted_eras[$era_id] = [ "era_start_time" => $era_start_time, - "addresses" => array() - ); + "addresses" => [] + ]; } - $sorted_eras - [$era_id] - ["addresses"] - [$public_key] = array( + $sorted_eras[$era_id]["addresses"][$public_key] = [ "in_pool" => $era->in_auction, - "rewards" => $era->uptime, - ); + "rewards" => $era->uptime + ]; } $return["eras"] = $sorted_eras; - $column_count = 0; + $column_count = 0; foreach ($return["eras"] as $era) { $count = $era["addresses"] ? count($era["addresses"]) : 0; - if ($count > $column_count) { $column_count = $count; } @@ -833,7 +790,6 @@ public function getMyEras() { $return["column_count"] = $column_count + 1; - // info($return); return $this->successResponse($return); } @@ -1681,44 +1637,24 @@ public function getVoteDetail($id) public function canVote() { - $user = auth()->user(); + $user = auth()->user(); $user_id = $user->id; - $return = array( - 'setting_voting_eras' => 0, + $return = [ + 'setting_voting_eras' => 0, 'setting_good_standing_eras' => 0, - 'good_standing_eras' => 0, - 'total_active_eras' => 0, + 'good_standing_eras' => 0, + 'total_active_eras' => 0, 'can_vote' => false - ); - - // current era - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + ]; - // get settings - $voting_eras_to_vote = DB::select(" - SELECT value - FROM settings - WHERE name = 'voting_eras_to_vote' - "); - $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); - $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); + $current_era_id = Helper::getCurrentERAId(); + $settings = Helper::getSettings(); - $voting_eras_since_redmark = DB::select(" - SELECT value - FROM settings - WHERE name = 'voting_eras_since_redmark' - "); - $voting_eras_since_redmark = $voting_eras_since_redmark[0] ?? array(); - $voting_eras_since_redmark = (int)($voting_eras_since_redmark->value ?? 0); + $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 0; + $voting_eras_since_redmark = isset($settings['voting_eras_since_redmark']) ? (int) $settings['voting_eras_since_redmark'] : 0; - $return['setting_voting_eras'] = $voting_eras_to_vote; + $return['setting_voting_eras'] = $voting_eras_to_vote; $return['setting_good_standing_eras'] = $voting_eras_since_redmark; $user_addresses = DB::select(" @@ -1726,14 +1662,13 @@ public function canVote() FROM user_addresses WHERE user_id = $user_id "); - $user_addresses = $user_addresses ?? array(); + $user_addresses = $user_addresses ?? []; foreach ($user_addresses as $a) { $p = $a->public_address_node ?? ''; // find smallest number of eras since public_key encountered a bad mark - // good_standing_eras - $good_standing_eras = DB::select(" + $temp = DB::select(" SELECT era_id FROM all_node_data2 WHERE public_key = '$p' @@ -1744,28 +1679,24 @@ public function canVote() ORDER BY era_id DESC LIMIT 1 "); - $good_standing_eras = $good_standing_eras[0] ?? array(); - $good_standing_eras = (int)($good_standing_eras->era_id ?? 0); - $good_standing_eras = $current_era_id - $good_standing_eras; - - if ($good_standing_eras < 0) { - $good_standing_eras = 0; - } + if (!$temp) $temp = []; + $good_standing_eras = 0; + if (isset($temp[0])) $good_standing_eras = (int) ($temp[0]->era_id ?? 0); + if ($current_era_id > $good_standing_eras) $good_standing_eras = $current_era_id - $good_standing_eras; if ($good_standing_eras > $return['good_standing_eras']) { $return['good_standing_eras'] = $good_standing_eras; } // total_active_eras - $eras = DB::select(" - SELECT count(id) + $temp = DB::select(" + SELECT count(id) as tCount FROM all_node_data2 WHERE public_key = '$p' "); - - $eras = (array)($eras[0] ?? array()); - $eras = (int)($eras['count(id)'] ?? 0); - + if (!$temp) $temp = []; + $eras = 0; + if (isset($temp[0])) $eras = (int) ($temp[0]->tCount ?? 0); if ($current_era_id - $eras > $return['total_active_eras']) { $return['total_active_eras'] = $current_era_id - $eras; } diff --git a/tests/Feature/UserFunctionsTest.php b/tests/Feature/UserFunctionsTest.php index 45b180dd..2543f297 100644 --- a/tests/Feature/UserFunctionsTest.php +++ b/tests/Feature/UserFunctionsTest.php @@ -157,9 +157,9 @@ public function testGetMembershipPage() { 'Accept' => 'application/json', 'Authorization' => 'Bearer ' . $token, ])->json('get', '/api/v1/users/get-membership-page'); - + // $apiResponse = $response->baseResponse->getData(); - + $response->assertStatus(200) ->assertJsonStructure([ 'message', @@ -202,7 +202,20 @@ public function testGetMyEras() { } public function testCanVote() { + $token = $this->getUserToken(); + $response = $this->withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $token, + ])->json('get', '/api/v1/users/can-vote'); + + // $apiResponse = $response->baseResponse->getData(); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'message', + 'data', + ]); } public function testChangeEmail() { @@ -243,10 +256,6 @@ public function testGetProfile() { ]); } - public function testSendHellosignRequest() { - - } - public function testSubmitPublicAddress() { $token = $this->getUserToken(); From 1ebc3ac343113a0a54feafe598f5a62edd7e3564 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 26 Oct 2022 23:45:26 -0400 Subject: [PATCH 085/162] # Unit Test --- app/Console/Commands/CheckBallot.php | 3 +- app/Console/Helper.php | 14 +++++ app/Helpers/helper.php | 16 +----- .../Controllers/Api/V1/AdminController.php | 8 +-- tests/Feature/UserFunctionsTest.php | 6 +- tests/TestCase.php | 57 +++++++++++++++++-- 6 files changed, 72 insertions(+), 32 deletions(-) diff --git a/app/Console/Commands/CheckBallot.php b/app/Console/Commands/CheckBallot.php index 639b1008..d4459aca 100644 --- a/app/Console/Commands/CheckBallot.php +++ b/app/Console/Commands/CheckBallot.php @@ -2,6 +2,7 @@ namespace App\Console\Commands; +use App\Console\Helper; use App\Models\Ballot; use Carbon\Carbon; use Illuminate\Console\Command; @@ -39,7 +40,7 @@ public function __construct() */ public function handle() { - $settings = getSettings(); + $settings = Helper::getSettings(); $quorumRate = $settings['quorum_rate_ballot'] ?? 50; $now = Carbon::now('UTC'); $ballots = Ballot::with(['vote'])->where('status', 'active')->where('time_end', '<=', $now)->get(); diff --git a/app/Console/Helper.php b/app/Console/Helper.php index 9878962e..d4b998cb 100644 --- a/app/Console/Helper.php +++ b/app/Console/Helper.php @@ -31,6 +31,20 @@ public static function getSettings() { $settings[$item->name] = $item->value; } } + if (!isset($settings['peers'])) $settings['peers'] = 0; + if (!isset($settings['eras_look_back'])) $settings['eras_look_back'] = 1; + if (!isset($settings['eras_to_be_stable'])) $settings['eras_to_be_stable'] = 1; + if (!isset($settings['voting_eras_to_vote'])) $settings['voting_eras_to_vote'] = 1; + if (!isset($settings['uptime_calc_size'])) $settings['uptime_calc_size'] = 1; + if (!isset($settings['voting_eras_since_redmark'])) $settings['voting_eras_since_redmark'] = 1; + if (!isset($settings['uptime_warning'])) $settings['uptime_warning'] = 1; + if (!isset($settings['uptime_probation'])) $settings['uptime_probation'] = 1; + if (!isset($settings['uptime_correction_unit'])) $settings['uptime_correction_unit'] = 'Weeks'; + if (!isset($settings['uptime_correction_value'])) $settings['uptime_correction_value'] = 1; + if (!isset($settings['redmarks_revoke'])) $settings['redmarks_revoke'] = 1; + if (!isset($settings['redmarks_revoke_calc_size'])) $settings['redmarks_revoke_calc_size'] = 1; + if (!isset($settings['responsiveness_warning'])) $settings['responsiveness_warning'] = 1; + if (!isset($settings['responsiveness_probation'])) $settings['responsiveness_probation'] = 1; return $settings; } diff --git a/app/Helpers/helper.php b/app/Helpers/helper.php index 62044e23..fa615a5c 100644 --- a/app/Helpers/helper.php +++ b/app/Helpers/helper.php @@ -56,18 +56,4 @@ function total_ram_cpu_usage() 'usedmemInGB' => $usedmemInGB, 'load' => $load, ]; -} - -// Get Settings -function getSettings() -{ - // Get Settings - $settings = []; - $items = Setting::get(); - if ($items) { - foreach ($items as $item) { - $settings[$item->name] = $item->value; - } - } - return $settings; -} +} \ No newline at end of file diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index d05d0161..df65868a 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -1384,13 +1384,7 @@ public function getViewFileBallot(Request $request, $fileId) // Get Global Settings public function getGlobalSettings() { - $items = Setting::get(); - $settings = []; - if ($items) { - foreach ($items as $item) { - $settings[$item->name] = $item->value; - } - } + $settings = Helper::getSettings(); $ruleKycNotVerify = LockRules::where('type', 'kyc_not_verify') ->orderBy('id', 'ASC') diff --git a/tests/Feature/UserFunctionsTest.php b/tests/Feature/UserFunctionsTest.php index 2543f297..317f10f2 100644 --- a/tests/Feature/UserFunctionsTest.php +++ b/tests/Feature/UserFunctionsTest.php @@ -31,7 +31,7 @@ public function testGetCaKycHash() { ])->json('get', '/api/v1/members/ca-kyc-hash/AB10BC99'); // $apiResponse = $response->baseResponse->getData(); - + $response->assertStatus(200) ->assertJsonStructure([ 'message', @@ -576,7 +576,7 @@ public function testInfoDashboard() { /* public function testGetEarningByNode() { $node = '011117189c666f81c5160cd610ee383dc9b2d0361f004934754d39752eedc64957'; - $token = $this->getUserToken($node); + $token = $this->getUserToken(); $response = $this->withHeaders([ 'Accept' => 'application/json', @@ -596,7 +596,7 @@ public function testGetEarningByNode() { /* public function testGetChartEarningByNode() { $node = '011117189c666f81c5160cd610ee383dc9b2d0361f004934754d39752eedc64957'; - $token = $this->getUserToken($node); + $token = $this->getUserToken(); $response = $this->withHeaders([ 'Accept' => 'application/json', diff --git a/tests/TestCase.php b/tests/TestCase.php index 19de1b90..ab8ee062 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -10,9 +10,11 @@ use Illuminate\Support\Facades\Date; use App\Models\User; +use App\Models\UserAddress; use App\Models\Profile; use App\Models\VerifyUser; use App\Models\PagePermission; +use App\Models\AllNodeData2; abstract class TestCase extends BaseTestCase { @@ -24,6 +26,36 @@ public function setUp(): void Artisan::call('config:clear'); Artisan::call('cache:clear'); Artisan::call('passport:install'); + + $key = '011117189c666f81c5160cd610ee383dc9b2d0361f004934754d39752eedc64957'; + $eraId = 5537; + + $record = AllNodeData2::where('public_key', $key) + ->where('era_id', $eraId) + ->first(); + if (!$record) { + $record = new AllNodeData2; + $record->public_key = $key; + $record->era_id = $eraId; + $record->uptime = 99.77; + $record->current_era_weight = 320860571; + $record->next_era_weight = 320868080; + $record->in_current_era = 1; + $record->in_next_era = 1; + $record->in_auction = 1; + $record->bid_delegators_count = 173; + $record->bid_delegation_rate = 10; + $record->bid_inactive = 0; + $record->bid_self_staked_amount = 2281468; + $record->bid_delegators_staked_amount = 318586612; + $record->bid_total_staked_amount = 320868080; + $record->port8888_peers = 0; + $record->port8888_era_id = null; + $record->port8888_block_height = null; + $record->port8888_build_version = null; + $record->port8888_next_upgrade = null; + $record->save(); + } } public function addAdmin() { @@ -60,7 +92,9 @@ public function getAdminToken() { return null; } - public function addUser($node = null) { + public function addUser() { + $node = '011117189c666f81c5160cd610ee383dc9b2d0361f004934754d39752eedc64957'; + $first_name = 'Test'; $last_name = 'Individual'; $email = 'testindividual@gmail.com'; @@ -82,9 +116,20 @@ public function addUser($node = null) { $user->signature_request_id = 'TestSignatureRequestId'; $user->role = 'member'; $user->letter_file = 'LetterFileLink'; - if ($node) $user->public_address_node = $node; + $user->public_address_node = $node; $user->save(); } + + $userAddress = UserAddress::where('user_id', $user->id) + ->where('public_address_node', $node) + ->first(); + if (!$userAddress) { + $userAddress = new UserAddress; + $userAddress->user_id = $user->id; + $userAddress->public_address_node = $node; + $userAddress->save(); + } + return $user; } @@ -106,8 +151,8 @@ public function unBlockAccess($userId, $name) { $permission->save(); } - public function getUserTokenData($node = null) { - $user = $this->addUser($node); + public function getUserTokenData() { + $user = $this->addUser(); $params = [ 'email' => 'testindividual@gmail.com', @@ -125,8 +170,8 @@ public function getUserTokenData($node = null) { return null; } - public function getUserToken($node = null) { - $this->addUser($node); + public function getUserToken() { + $this->addUser(); $params = [ 'email' => 'testindividual@gmail.com', From eb866e86fe28fdc97a951466f41b14624687b719 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 27 Oct 2022 05:44:09 -0400 Subject: [PATCH 086/162] # Unit Test --- app/Console/Commands/CheckNodeStatus.php | 2 +- .../Controllers/Api/V1/AdminController.php | 397 ++++++------------ .../Controllers/Api/V1/UserController.php | 17 +- routes/api.php | 2 +- tests/Feature/AdminFunctionsTest.php | 56 ++- 5 files changed, 197 insertions(+), 277 deletions(-) diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index 65d2babf..65bbe49a 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -102,7 +102,7 @@ public function handle() $hasOnline = true; if (isset($settings['uptime_probation']) && (float) $settings['uptime_probation'] > 0) { - $uptime_calc_size = $settings['uptime_calc_size'] ?? 0; + $uptime_calc_size = $settings['uptime_calc_size'] ?? 1; $uptime_calc_size = (int) $uptime_calc_size; $uptime = (float) $temp->uptime; diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index df65868a..3e89c69b 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -65,6 +65,7 @@ class AdminController extends Controller { public function allErasUser($id) { $user = User::where('id', $id)->first(); + $user_id = $id; if (!$user || $user->role == 'admin') { return $this->errorResponse( @@ -73,56 +74,19 @@ public function allErasUser($id) { ); } - $user_id = $id; - - // Get current era - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + $current_era_id = Helper::getCurrentERAId(); + $settings = Helper::getSettings(); - // define return object - $return = array( + $return = [ "column_count" => 2, - "eras" => array( - array( - "era_start_time" => '', - "addresses" => array( - "0123456789abcdef" => array( - "in_pool" => false, - "rewards" => 0 - ) - ) - ) - ), + "eras" => [], "addresses" => [] - ); - - unset($return["eras"][0]); - - // get settings - $voting_eras_to_vote = DB::select(" - SELECT value - FROM settings - WHERE name = 'voting_eras_to_vote' - "); - $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); - $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); + ]; - $uptime_calc_size = DB::select(" - SELECT value - FROM settings - WHERE name = 'uptime_calc_size' - "); - $uptime_calc_size = $uptime_calc_size[0] ?? array(); - $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); + $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; + $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 1; - // get eras table data $era_minus_360 = $current_era_id - $uptime_calc_size; - if ($era_minus_360 < 1) { $era_minus_360 = 1; } @@ -138,10 +102,7 @@ public function allErasUser($id) { AND b.user_id = $user_id ORDER BY a.era_id DESC "); - - if (!$addresses) { - $addresses = []; - } + if (!$addresses) $addresses = []; foreach ($addresses as $address) { $return['addresses'][] = $address->public_key; @@ -161,33 +122,27 @@ public function allErasUser($id) { AND era_id > $era_minus_360 ORDER BY a.era_id DESC "); + if (!$eras) $eras = []; - if (!$eras) { - $eras = array(); - } - - $sorted_eras = array(); + $sorted_eras = []; // for each node address's era foreach ($eras as $era) { - $era_id = $era->era_id ?? 0; - $era_start_time = $era->created_at ?? ''; - $public_key = $era->public_key; + $era_id = $era->era_id ?? 0; + $era_start_time = $era->created_at ?? ''; + $public_key = $era->public_key; if (!isset($sorted_eras[$era_id])) { - $sorted_eras[$era_id] = array( - "era_start_time" => $era_start_time, - "addresses" => array() - ); + $sorted_eras[$era_id] = [ + "era_start_time" => $era_start_time, + "addresses" => [] + ]; } - $sorted_eras - [$era_id] - ["addresses"] - [$public_key] = array( + $sorted_eras[$era_id]["addresses"][$public_key] = [ "in_pool" => $era->in_auction, "rewards" => $era->uptime, - ); + ]; } $return["eras"] = $sorted_eras; @@ -195,7 +150,6 @@ public function allErasUser($id) { foreach ($return["eras"] as $era) { $count = $era["addresses"] ? count($era["addresses"]) : 0; - if ($count > $column_count) { $column_count = $count; } @@ -203,42 +157,19 @@ public function allErasUser($id) { $return["column_count"] = $column_count + 1; - // info($return); return $this->successResponse($return); } public function allEras() { - // Get current era - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + $current_era_id = Helper::getCurrentERAId(); + $settings = Helper::getSettings(); // define return object - $return = array( - "addresses" => array( - "0123456789abcdef" => array( - "uptime" => 0, - "eras_active" => 0, - "eras_since_bad_mark" => $current_era_id, - "total_bad_marks" => 0 - ) - ), - "users" => array( - array( - "user_id" => 0, - "name" => "Jason Stone", - "pseudonym" => "jsnstone" - ) - ) - ); + $return = [ + "addresses" => [], + "users" => [] + ]; - unset($return["addresses"]["0123456789abcdef"]); - unset($return['users']); - // get addresses data $addresses = DB::select(" SELECT @@ -248,33 +179,16 @@ public function allEras() { ON a.public_key = b.public_address_node WHERE a.era_id = $current_era_id "); + if (!$addresses) $addresses = []; - if (!$addresses) { - $addresses = array(); - } - - // get settings - $voting_eras_to_vote = DB::select(" - SELECT value - FROM settings - WHERE name = 'voting_eras_to_vote' - "); - $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); - $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); - - $uptime_calc_size = DB::select(" - SELECT value - FROM settings - WHERE name = 'uptime_calc_size' - "); - $uptime_calc_size = $uptime_calc_size[0] ?? array(); - $uptime_calc_size = (int)($uptime_calc_size->value ?? 0); + $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; + $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 1; // for each member's node address foreach ($addresses as $address) { $p = $address->public_key ?? ''; - $total_bad_marks = DB::select(" + $temp = DB::select(" SELECT era_id FROM all_node_data2 WHERE public_key = '$p' @@ -284,55 +198,56 @@ public function allEras() { ) ORDER BY era_id DESC "); + if (!$temp) $temp = []; - $eras_since_bad_mark = $total_bad_marks[0] ?? array(); - $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); - $total_bad_marks = count((array)$total_bad_marks); + $eras_since_bad_mark = $current_era_id; + if (isset($temp[0])) $eras_since_bad_mark = $current_era_id - (int) ($temp[0]->era_id ?? 0); + $total_bad_marks = count($temp); - $eras_active = DB::select(" + $temp = DB::select(" SELECT era_id FROM all_node_data2 WHERE public_key = '$p' ORDER BY era_id ASC LIMIT 1 "); + if (!$temp) $temp = []; - $eras_active = (int)($eras_active[0]->era_id ?? 0); + $eras_active = 0; + if (isset($temp[0])) $eras_active = (int) ($temp[0]->era_id ?? 0); + if ($eras_active < $current_era_id) $eras_active = $current_era_id - $eras_active; // Calculate historical_performance from past $uptime_calc_size eras $missed = 0; - $in_current_eras = DB::select(" + $temp = DB::select(" SELECT in_current_era FROM all_node_data2 WHERE public_key = '$p' ORDER BY era_id DESC LIMIT $uptime_calc_size "); + if (!$temp) $temp = []; - $in_current_eras = $in_current_eras ? $in_current_eras : array(); - $window = $in_current_eras ? count($in_current_eras) : 0; - - foreach ($in_current_eras as $c) { - $in = (bool)($c->in_current_era ?? 0); + $window = count($temp); + foreach ($temp as $c) { + $in = (bool) ($c->in_current_era ?? 0); if (!$in) { $missed += 1; } } - $uptime = (float)($address->uptime ?? 0); - $historical_performance = round( - ($uptime * ($window - $missed)) / $window, - 2, - PHP_ROUND_HALF_UP - ); + $uptime = (float) ($address->uptime ?? 0); + $value = $uptime; + if ($window > 0) $value = (float) ($uptime * ($window - $missed) / $window); + $historical_performance = round($value, 2); - $return["addresses"][$p] = array( - "uptime" => $historical_performance, - "eras_active" => $current_era_id - $eras_active, + $return["addresses"][$p] = [ + "uptime" => $historical_performance, + "eras_active" => $eras_active, "eras_since_bad_mark" => $eras_since_bad_mark, - "total_bad_marks" => $total_bad_marks - ); + "total_bad_marks" => $total_bad_marks + ]; } $users = DB::select(" @@ -343,63 +258,29 @@ public function allEras() { AND a.has_address = 1 AND a.banned = 0; "); + if (!$users) $users = []; foreach ($users as $user) { - $return["users"][] = array( - "user_id" => $user->id, - "name" => $user->first_name . ' ' . $user->last_name, + $return["users"][] = [ + "user_id" => $user->id, + "name" => $user->first_name . ' ' . $user->last_name, "pseudonym" => $user->pseudonym, - ); - } - - if (!$users) { - $users = array(); + ]; } - // info($return); return $this->successResponse($return); } public function getNodesPage() { - // Get current era - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int)($current_era_id[0]->era_id ?? 0); + $current_era_id = Helper::getCurrentERAId(); // Define complete return object - $return = array( + $return = [ "mbs" => 0, - "ranking" => array( - "0123456789abcdef" => 0 - ), - "addresses" => array( - "0123456789abcdef" => array( - "stake_amount" => 0, - "delegators" => 0, - "uptime" => 0, - "update_responsiveness" => 100, - "peers" => 0, - "daily_earning" => 0, - "total_eras" => 0, - "eras_since_bad_mark" => $current_era_id, - "total_bad_marks" => 0, - "faling" => 0, - "validator_rewards" => array( - "day" => array(), - "week" => array(), - "month" => array(), - "year" => array() - ) - ) - ) - ); + "ranking" => [], + "addresses" => [] + ]; - unset($return["ranking"]["0123456789abcdef"]); - unset($return["addresses"]["0123456789abcdef"]); $nodeHelper = new NodeHelper(); // get ranking @@ -416,65 +297,44 @@ public function getNodesPage() { AND in_next_era = 1 AND in_auction = 1 "); + if (!$ranking) $ranking = []; + $max_delegators = 0; $max_stake_amount = 0; - foreach ($ranking as $r) { - if ((int)$r->bid_delegators_count > $max_delegators) { - $max_delegators = (int)$r->bid_delegators_count; + if ((int) $r->bid_delegators_count > $max_delegators) { + $max_delegators = (int) $r->bid_delegators_count; } - - if ((int)$r->bid_total_staked_amount > $max_stake_amount) { - $max_stake_amount = (int)$r->bid_total_staked_amount; + if ((int) $r->bid_total_staked_amount > $max_stake_amount) { + $max_stake_amount = (int) $r->bid_total_staked_amount; } } foreach ($ranking as $r) { - $uptime_score = ( - 25 * (float)$r->uptime - ) / 100; - $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; - - $fee_score = ( - 25 * - (1 - ((float)$r->bid_delegation_rate / 100)) - ); - $fee_score = $fee_score < 0 ? 0 : $fee_score; - - $count_score = ( - (float)$r->bid_delegators_count / - $max_delegators - ) * 25; - $count_score = $count_score < 0 ? 0 : $count_score; - - $stake_score = ( - (float)$r->bid_total_staked_amount / - $max_stake_amount - ) * 25; - $stake_score = $stake_score < 0 ? 0 : $stake_score; - - $return["ranking"][$r->public_key] = ( - $uptime_score + - $fee_score + - $count_score + - $stake_score - ); - } + $uptime_score = (float) (25 * (float) $r->uptime / 100); + $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; - uasort( - $return["ranking"], - function($x, $y) { - if ($x == $y) { - return 0; - } + $fee_score = 25 * (1 - (float) ($r->bid_delegation_rate / 100)); + $fee_score = $fee_score < 0 ? 0 : $fee_score; + + $count_score = (float) ($r->bid_delegators_count / $max_delegators) * 25; + $count_score = $count_score < 0 ? 0 : $count_score; + + $stake_score = (float) ($r->bid_total_staked_amount / $max_stake_amount) * 25; + $stake_score = $stake_score < 0 ? 0 : $stake_score; + + $return["ranking"][$r->public_key] = $uptime_score + $fee_score + $count_score + $stake_score; + } - return ($x > $y) ? -1 : 1; + uasort($return["ranking"], function($x, $y) { + if ($x == $y) { + return 0; } - ); + return ($x > $y) ? -1 : 1; + }); - $sorted_ranking = array(); + $sorted_ranking = []; $i = 1; - foreach ($return["ranking"] as $public_key => $score) { $sorted_ranking[$public_key] = $i; $i += 1; @@ -496,16 +356,13 @@ function($x, $y) { ON b.user_id = c.id WHERE a.era_id = $current_era_id "); - - if (!$addresses) { - $addresses = array(); - } + if (!$addresses) $addresses = []; // for each member's node address foreach ($addresses as $address) { $a = $address->public_key ?? ''; - $total_bad_marks = DB::select(" + $temp = DB::select(" SELECT era_id FROM all_node_data2 WHERE public_key = '$a' @@ -515,25 +372,28 @@ function($x, $y) { ) ORDER BY era_id DESC "); + if (!$temp) $temp = []; - $eras_since_bad_mark = $total_bad_marks[0] ?? array(); - $eras_since_bad_mark = $current_era_id - (int)($eras_since_bad_mark->era_id ?? 0); - $total_bad_marks = count((array)$total_bad_marks); + $eras_since_bad_mark = $current_era_id; + if (isset($temp[0])) $eras_since_bad_mark = $current_era_id - (int) ($temp[0]->era_id ?? 0); + $total_bad_marks = count($temp); - $total_eras = DB::select(" + $temp = DB::select(" SELECT era_id FROM all_node_data2 WHERE public_key = '$a' ORDER BY era_id ASC LIMIT 1 "); + if (!$temp) $temp = []; - $total_eras = (int)($total_eras[0]->era_id ?? 0); - $total_eras = $current_era_id - $total_eras; + $total_eras = 0; + if (isset($temp[0])) $total_eras = (int) ($temp[0]->era_id ?? 0); + if ($current_era_id > $total_eras) $total_eras = $current_era_id - $total_eras; // Calc earning - $one_day_ago = Carbon::now('UTC')->subHours(24); - $daily_earning = DB::select(" + $one_day_ago = Carbon::now('UTC')->subHours(24); + $temp = DB::select(" SELECT bid_self_staked_amount FROM all_node_data2 WHERE public_key = '$a' @@ -541,14 +401,17 @@ function($x, $y) { ORDER BY era_id DESC LIMIT 1 "); - $daily_earning = $daily_earning[0]->bid_self_staked_amount ?? 0; - $daily_earning = $address->bid_self_staked_amount - $daily_earning; + if (!$temp) $temp = []; + + $daily_earning = 0; + if (isset($temp[0])) $daily_earning = (float) ($temp[0]->bid_self_staked_amount ?? 0); + $daily_earning = (float) $address->bid_self_staked_amount - $daily_earning; $daily_earning = $daily_earning < 0 ? 0 : $daily_earning; - $earning_day = $nodeHelper->getValidatorRewards($a, 'day'); - $earning_week = $nodeHelper->getValidatorRewards($a, 'week'); + $earning_day = $nodeHelper->getValidatorRewards($a, 'day'); + $earning_week = $nodeHelper->getValidatorRewards($a, 'week'); $earning_month = $nodeHelper->getValidatorRewards($a, 'month'); - $earning_year = $nodeHelper->getValidatorRewards($a, 'year'); + $earning_year = $nodeHelper->getValidatorRewards($a, 'year'); if ( $address->in_current_era == 0 || @@ -559,36 +422,38 @@ function($x, $y) { $failing = 0; } - $return["addresses"][$a] = array( - "stake_amount" => $address->bid_total_staked_amount, - "delegators" => $address->bid_delegators_count, - "uptime" => $address->uptime, + $return["addresses"][$a] = [ + "stake_amount" => $address->bid_total_staked_amount, + "delegators" => $address->bid_delegators_count, + "uptime" => $address->uptime, "update_responsiveness" => 100, - "peers" => $address->peers, - "daily_earning" => $daily_earning, - "total_eras" => $total_eras, - "eras_since_bad_mark" => $eras_since_bad_mark, - "total_bad_marks" => $total_bad_marks, - "failing" => $failing, - "validator_rewards" => array( - "day" => $earning_day, - "week" => $earning_week, - "month" => $earning_month, - "year" => $earning_year - ) - ); + "peers" => $address->peers, + "daily_earning" => $daily_earning, + "total_eras" => $total_eras, + "eras_since_bad_mark" => $eras_since_bad_mark, + "total_bad_marks" => $total_bad_marks, + "failing" => $failing, + "validator_rewards" => [ + "day" => $earning_day, + "week" => $earning_week, + "month" => $earning_month, + "year" => $earning_year + ] + ]; } // get mbs - $mbs = DB::select(" + $temp = DB::select(" SELECT mbs FROM mbs ORDER BY era_id DESC LIMIT 1 "); - $return["mbs"] = (int)($mbs[0]->mbs ?? 0); + if (!$temp) $temp = []; + + $return['mbs'] = 0; + if (isset($temp[0])) $return['mbs'] = (int) ($temp[0]->mbs ?? 0); - // info($return); return $this->successResponse($return); } diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 3b584391..1d2517b4 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -231,8 +231,8 @@ public function getUserDashboard() { $settings = Helper::getSettings(); - $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 0; - $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 0; + $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; + $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 1; // for each address belonging to a user foreach ($addresses as $address) { @@ -358,7 +358,7 @@ public function getMembershipPage() { "); if (!$addresses) $addresses = []; - $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) ($settings['uptime_calc_size']) : 0; + $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) ($settings['uptime_calc_size']) : 1; foreach ($addresses as $address) { $p = $address->public_key ?? ''; @@ -524,7 +524,7 @@ public function getNodesPage() { "); if (!$addresses) $addresses = []; - $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 0; + $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 1; // for each member's node address foreach ($addresses as $address) { @@ -631,6 +631,7 @@ public function getNodesPage() { LIMIT 1 "); if (!$temp) $temp = []; + $return['mbs'] = 0; if (isset($temp[0])) $return['mbs'] = (int) ($temp[0]->mbs ?? 0); @@ -666,8 +667,8 @@ public function getMyEras() { if (!$addresses) $addresses = []; // get settings - $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 0; - $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 0; + $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; + $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 1; // for each member's node address foreach ($addresses as $address) { @@ -1651,8 +1652,8 @@ public function canVote() $current_era_id = Helper::getCurrentERAId(); $settings = Helper::getSettings(); - $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 0; - $voting_eras_since_redmark = isset($settings['voting_eras_since_redmark']) ? (int) $settings['voting_eras_since_redmark'] : 0; + $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; + $voting_eras_since_redmark = isset($settings['voting_eras_since_redmark']) ? (int) $settings['voting_eras_since_redmark'] : 1; $return['setting_voting_eras'] = $voting_eras_to_vote; $return['setting_good_standing_eras'] = $voting_eras_since_redmark; diff --git a/routes/api.php b/routes/api.php index a6c7471b..2e0c5d54 100644 --- a/routes/api.php +++ b/routes/api.php @@ -117,7 +117,7 @@ Route::prefix('admin')->middleware(['role_admin'])->group(function () { // New Nodes page endpoint Route::get('/users/get-nodes-page', [AdminController::class, 'getNodesPage']); - + // New Eras page endpoint Route::get('/users/all-eras', [AdminController::class, 'allEras']); diff --git a/tests/Feature/AdminFunctionsTest.php b/tests/Feature/AdminFunctionsTest.php index 2ed3dc49..440542d6 100644 --- a/tests/Feature/AdminFunctionsTest.php +++ b/tests/Feature/AdminFunctionsTest.php @@ -21,11 +21,65 @@ public function testConsoleCommand() { 'Accept' => 'application/json', 'Authorization' => 'Bearer ' . $token, ])->json('post', '/api/v1/users/submit-public-address', $params); - + $this->artisan('node-info')->assertSuccessful(); } */ + public function testGetUserNodesPage() { + $this->addUser(); + $token = $this->getAdminToken(); + + $response = $this->withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $token, + ])->json('get', '/api/v1/admin/users/get-nodes-page'); + + // $apiResponse = $response->baseResponse->getData(); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'message', + 'data', + ]); + } + + public function testAllERAs() { + $this->addUser(); + $token = $this->getAdminToken(); + + $response = $this->withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $token, + ])->json('get', '/api/v1/admin/users/all-eras'); + + // $apiResponse = $response->baseResponse->getData(); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'message', + 'data', + ]); + } + + public function testAllErasUser() { + $user = $this->addUser(); + $token = $this->getAdminToken(); + + $response = $this->withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $token, + ])->json('get', '/api/v1/admin/users/all-eras-user/' . $user->id); + + // $apiResponse = $response->baseResponse->getData(); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'message', + 'data', + ]); + } + public function testGetGraphInfo() { $response = $this->withHeaders([ 'Accept' => 'application/json', From 8e4297980b221b5f8dda2c2fe6bf982b6da15cfb Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 27 Oct 2022 09:41:05 -0400 Subject: [PATCH 087/162] # Unit Test --- .../Controllers/Api/V1/AdminController.php | 8 ++++---- tests/Feature/AdminFunctionsTest.php | 20 ++++++++++++++++++- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 3e89c69b..2a4bff8f 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -854,11 +854,11 @@ public function infoDashboard(Request $request) public function bypassApproveKYC($user_id) { - $user_id = (int) $user_id; - $user = User::find($user_id); - $now = Carbon::now('UTC'); + $user_id = (int) $user_id; + $user = User::find($user_id); + $now = Carbon::now('UTC'); $admin_user = auth()->user(); - + if ($user && $user->role == 'member') { $user->kyc_verified_at = $now; $user->approve_at = $now; diff --git a/tests/Feature/AdminFunctionsTest.php b/tests/Feature/AdminFunctionsTest.php index 440542d6..8bd701c6 100644 --- a/tests/Feature/AdminFunctionsTest.php +++ b/tests/Feature/AdminFunctionsTest.php @@ -93,7 +93,25 @@ public function testGetGraphInfo() { 'data', ]); } - + + public function testBypassApproveKYC() { + $user = $this->addUser(); + $token = $this->getAdminToken(); + + $response = $this->withHeaders([ + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $token, + ])->json('post', '/api/v1/admin/users/bypass-approve-kyc/' . $user->id); + + // $apiResponse = $response->baseResponse->getData(); + + $response->assertStatus(200) + ->assertJsonStructure([ + 'message', + 'data', + ]); + } + public function testGetUsers() { $token = $this->getAdminToken(); From b06b69afef2f5857e54b03ab0cfa666a56b6170f Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 27 Oct 2022 09:46:02 -0400 Subject: [PATCH 088/162] # Updates --- .../Controllers/Api/V1/AdminController.php | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 2a4bff8f..e8b84fa7 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -858,7 +858,7 @@ public function bypassApproveKYC($user_id) $user = User::find($user_id); $now = Carbon::now('UTC'); $admin_user = auth()->user(); - + if ($user && $user->role == 'member') { $user->kyc_verified_at = $now; $user->approve_at = $now; @@ -1767,7 +1767,7 @@ public function getVerificationUsers(Request $request) // Reset KYC public function resetKYC($id, Request $request) { - $admin = auth()->user(); + $admin = auth()->user(); $message = trim($request->get('message')); if (!$message) { @@ -1791,8 +1791,8 @@ public function resetKYC($id, Request $request) DocumentFile::where('user_id', $user->id)->delete(); $user->kyc_verified_at = null; - $user->approve_at = null; - $user->reset_kyc = 1; + $user->approve_at = null; + $user->reset_kyc = 1; $user->save(); Mail::to($user->email)->send(new AdminAlert( @@ -1970,7 +1970,7 @@ public function addEmailerAdmin(Request $request) ]; } - $record = new EmailerAdmin; + $record = new EmailerAdmin; $record->email = $email; $record->save(); @@ -1988,9 +1988,9 @@ public function deleteEmailerAdmin($adminId, Request $request) // Get Emailer Data public function getEmailerData(Request $request) { - $user = Auth::user(); - $data = []; - $admins = EmailerAdmin::where('id', '>', 0) + $user = Auth::user(); + $data = []; + $admins = EmailerAdmin::where('id', '>', 0) ->orderBy('email', 'asc') ->get(); @@ -1998,12 +1998,12 @@ public function getEmailerData(Request $request) ->orderBy('id', 'asc') ->get(); - $triggerUser = EmailerTriggerUser::where('id', '>', 0) + $triggerUser = EmailerTriggerUser::where('id', '>', 0) ->orderBy('id', 'asc') ->get(); $data = [ - 'admins' => $admins, + 'admins' => $admins, 'triggerAdmin' => $triggerAdmin, 'triggerUser' => $triggerUser, ]; @@ -2021,7 +2021,7 @@ public function updateEmailerTriggerAdmin($recordId, Request $request) $record = EmailerTriggerAdmin::find($recordId); if ($record) { - $enabled = (int)$request->get('enabled'); + $enabled = (int) $request->get('enabled'); $record->enabled = $enabled; $record->save(); return ['success' => true]; @@ -2033,12 +2033,12 @@ public function updateEmailerTriggerAdmin($recordId, Request $request) // Update Emailer Trigger User public function updateEmailerTriggerUser($recordId, Request $request) { - $user = Auth::user(); + $user = Auth::user(); $record = EmailerTriggerUser::find($recordId); if ($record) { - $enabled = (int)$request->get('enabled'); - $content = $request->get('content'); + $enabled = (int) $request->get('enabled'); + $content = $request->get('content'); $record->enabled = $enabled; if ($content) { @@ -2063,7 +2063,7 @@ public function updateLockRules(Request $request, $id) return $this->validateResponse($validator->errors()); } - $rule = LockRules::where('id', $id)->first(); + $rule = LockRules::where('id', $id)->first(); $rule->is_lock = $request->is_lock; $rule->save(); @@ -2073,16 +2073,16 @@ public function updateLockRules(Request $request, $id) // Get GraphInfo public function getGraphInfo(Request $request) { - $user = Auth::user(); + $user = Auth::user(); $graphDataDay = $graphDataWeek = $graphDataMonth = $graphDataYear = []; - $timeDay = Carbon::now('UTC')->subHours(24); - $timeWeek = Carbon::now('UTC')->subDays(7); + $timeDay = Carbon::now('UTC')->subHours(24); + $timeWeek = Carbon::now('UTC')->subDays(7); $timeMonth = Carbon::now('UTC')->subDays(30); - $timeYear = Carbon::now('UTC')->subYear(); + $timeYear = Carbon::now('UTC')->subYear(); $items = TokenPrice::orderBy('created_at', 'desc') ->where('created_at', '>=', $timeDay) From 556004a10370243b06cfd088398a35686ee05b66 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 27 Oct 2022 12:28:10 -0400 Subject: [PATCH 089/162] ENABLE retrieveGlobalUptime --- app/Services/NodeHelper.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Services/NodeHelper.php b/app/Services/NodeHelper.php index 60bcde1d..5c6888b6 100644 --- a/app/Services/NodeHelper.php +++ b/app/Services/NodeHelper.php @@ -55,7 +55,7 @@ public function decodePeers($__peers) } */ - /* + public function retrieveGlobalUptime($this_era_id) { $total_data = array(); @@ -117,7 +117,7 @@ public function retrieveGlobalUptime($this_era_id) return $total_data; } - */ + /* public function discoverPeers() From 0389d0fe1dfe31102664a9f1ce55362e0455d88b Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 3 Nov 2022 11:15:36 -0400 Subject: [PATCH 090/162] historic data cleanup --- app/Console/Commands/HistoricalData.php | 577 ------------------------ 1 file changed, 577 deletions(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index 0b854b2e..8e0cc05e 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -40,13 +40,6 @@ public function handle() /* casper-client get-auction-info --node-address http://18.219.70.138:7777/rpc -b e549a8703cb3bdf2c8e11a7cc72592c3043fe4490737064c6fd9da30cc0d4f36 | jq .result.auction_state.era_validators | jq .[0].era_id - - 011117189c666f81c5160cd610ee383dc9b2d0361f004934754d39752eedc64957 - - 34 seconds per era - 136 seconds per era - - ~100 block per era */ $get_block = 'casper-client get-block '; @@ -56,8 +49,6 @@ public function handle() $json = shell_exec($get_block.$node_arg); $json = json_decode($json); $current_era = (int)($json->result->block->header->era_id ?? 0); - // $historic_era = 4135; // pre-calculated - // $historic_block = 614408; // pre-calculated $historic_era = DB::select(" SELECT era_id @@ -411,574 +402,6 @@ public function handle() } } - - - - - - - - - - - - - - - - - - - /* - OLD - - - - $get_block = 'casper-client get-block '; - $get_auction = 'casper-client get-auction-info '; - $node_arg = '--node-address http://18.219.70.138:7777/rpc '; - - $json = shell_exec($get_block.$node_arg); - $json = json_decode($json); - $current_era = (int)($json->result->block->header->era_id ?? 0); - // $historic_era = 4135; // pre-calculated - // $historic_block = 614408; // pre-calculated - $historic_era = 4874; // bookmark - $historic_block = 775014; // bookmark - // $historic_era = 5701; // bookmark - // $historic_block = 955831; // bookmark - - // current seconds per era: 296 - - $i = 0; - - while ($current_era > $historic_era) { - // while ($i < 5000) { - // first see if we have this era's auction info - $node_data = DB::select(" - SELECT era_id - FROM all_node_data - WHERE era_id = $historic_era - "); - $node_data = (int)($node_data[0]->era_id ?? 0); - - if ($node_data) { - // era's auction info exists. do not need to fetch. - info('Already have era '.$historic_era.' data. skipping'); - $historic_era += 1; - } else { - // decrease block height and check for new switch block - info('Checking block '.$historic_block.' for era '.$historic_era); - $command = $get_block.$node_arg.'-b '.$historic_block; - $switch_block = shell_exec($command); - $json = json_decode($switch_block); - $era_id = $json->result->block->header->era_id ?? 0; - $block_hash = $json->result->block->hash ?? ''; - - if ($era_id == $historic_era) { - // start timer - $start_time = (int)time(); - - // get auction info for this new detected era switch - info($era_id.' '.$block_hash); - $historic_era += 1; - $command = $get_auction.$node_arg.'-b '.$block_hash; - $auction_info = shell_exec($command); - - - // very large object. aprx 10MB - $decoded_response = json_decode($auction_info); - $auction_state = $decoded_response->result->auction_state ?? array(); - $bids = $auction_state->bids ?? array(); - - // get era ID - $era_validators = $auction_state->era_validators ?? array(); - $current_era_validators = $era_validators[0] ?? array(); - $next_era_validators = $era_validators[1] ?? array(); - $current_era_id = (int)($current_era_validators->era_id ?? 0); - $next_era_id = (int)($next_era_validators->era_id ?? 0); - - info('Data aquired for era: '.$current_era_id); - - // loop current era - $current_validator_weights = $current_era_validators->validator_weights ?? array(); - info('Saving current era validator weights'); - - foreach ($current_validator_weights as $v) { - $public_key = $v->public_key ?? ''; - $weight = (int)($v->weight / 1000000000 ?? 0); - - $check = DB::select(" - SELECT public_key, era_id - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id = $current_era_id - "); - $check = $check[0] ?? null; - - if (!$check) { - DB::table('all_node_data')->insert( - array( - 'public_key' => $public_key, - 'era_id' => $current_era_id, - 'current_era_weight' => $weight, - 'in_curent_era' => 1, - 'created_at' => Carbon::now('UTC') - ) - ); - } - } - - // loop next era - $next_validator_weights = $next_era_validators->validator_weights ?? array(); - info('Saving next era validator weights'); - - foreach ($next_validator_weights as $v) { - $public_key = $v->public_key ?? ''; - $weight = (int)($v->weight / 1000000000 ?? 0); - - $check = DB::select(" - SELECT public_key, era_id - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id = $current_era_id - "); - $check = $check[0] ?? null; - - if (!$check) { - // info('Saved: '.$public_key); - DB::table('all_node_data')->insert( - array( - 'public_key' => $public_key, - 'era_id' => $current_era_id, - 'next_era_weight' => $weight, - 'in_next_era' => 1 - ) - ); - } else { - // info('Updated: '.$public_key); - DB::table('all_node_data') - ->where('public_key', $public_key) - ->where('era_id', $current_era_id) - ->update( - array( - 'next_era_weight' => $weight, - 'in_next_era' => 1 - ) - ); - } - } - - // set MBS array. minimum bid slot amount - $MBS_arr = array(); - - // get global uptimes from MAKE - $global_uptime = $nodeHelper->retrieveGlobalUptime($current_era_id); - - // loop auction era - info('Looping auction era - Saving uptime, bid, and daily earnings data'); - foreach ($bids as $b) { - $public_key = strtolower($b->public_key ?? 'nill'); - $bid = $b->bid ?? array(); - - // get self - $self_staked_amount = (int)($bid->staked_amount ?? 0); - $delegation_rate = (int)($bid->delegation_rate ?? 0); - $bid_inactive = (int)($bid->inactive ?? false); - - // calculate total stake, delegators + self stake - $delegators = (array)($bid->delegators ?? array()); - $delegators_count = count($delegators); - $delegators_staked_amount = 0; - - foreach ($delegators as $delegator) { - $delegators_staked_amount += (int)($delegator->staked_amount ?? 0); - } - - // convert and calculate stake amounts - $delegators_staked_amount = (int)($delegators_staked_amount / 1000000000); - $self_staked_amount = (int)($self_staked_amount / 1000000000); - $total_staked_amount = $delegators_staked_amount + $self_staked_amount; - - // append to MBS array and pluck 100th place later - $MBS_arr[$public_key] = $total_staked_amount; - - // get node uptime from MAKE object - $uptime = 0; - - foreach ($global_uptime as $uptime_array) { - $fvid = strtolower($uptime_array->public_key ?? ''); - - if($fvid == $public_key) { - $uptime = (float)($uptime_array->average_score ?? 0); - break; - } - } - - // record auction bid data for this node - $check = DB::select(" - SELECT public_key, era_id - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id = $current_era_id - "); - $check = $check[0] ?? null; - - if (!$check) { - // info('Saved: '.$public_key); - DB::table('all_node_data')->insert( - array( - 'public_key' => $public_key, - 'era_id' => $current_era_id, - 'uptime' => $uptime, - 'in_auction' => 1, - 'bid_delegators_count' => $delegators_count, - 'bid_delegation_rate' => $delegation_rate, - 'bid_inactive' => $bid_inactive, - 'bid_self_staked_amount' => $self_staked_amount, - 'bid_delegators_staked_amount' => $delegators_staked_amount, - 'bid_total_staked_amount' => $total_staked_amount - ) - ); - } else { - // info('Updated: '.$public_key); - DB::table('all_node_data') - ->where('public_key', $public_key) - ->where('era_id', $current_era_id) - ->update( - array( - 'uptime' => $uptime, - 'in_auction' => 1, - 'bid_delegators_count' => $delegators_count, - 'bid_delegation_rate' => $delegation_rate, - 'bid_inactive' => $bid_inactive, - 'bid_self_staked_amount' => $self_staked_amount, - 'bid_delegators_staked_amount' => $delegators_staked_amount, - 'bid_total_staked_amount' => $total_staked_amount - ) - ); - } - - // save current stake amount to daily earnings table - $earning = new DailyEarning(); - $earning->node_address = $public_key; - $earning->self_staked_amount = (int)$self_staked_amount; - $earning->created_at = Carbon::now('UTC'); - $earning->save(); - - // get difference between current self stake and yesterdays self stake - $get_earning = DailyEarning::where('node_address', $public_key) - ->where('created_at', '>', Carbon::now('UTC')->subHours(24)) - ->orderBy('created_at', 'asc') - ->first(); - - $yesterdays_self_staked_amount = (int)($get_earning->self_staked_amount ?? 0); - $daily_earning = $self_staked_amount - $yesterdays_self_staked_amount; - - // look for existing peer by public key for port 8888 data - foreach ($port8888_responses as $port8888data) { - $our_public_signing_key = strtolower($port8888data['our_public_signing_key'] ?? ''); - - if ($our_public_signing_key == $public_key) { - // found in peers - $peer_count = (int)($port8888data['peer_count'] ?? 0); - $era_id = (int)($port8888data['last_added_block_info']['era_id'] ?? 0); - $block_height = (int)($port8888data['last_added_block_info']['height'] ?? 0); - $build_version = $port8888data['build_version'] ?? '1.0.0'; - $build_version = explode('-', $build_version)[0]; - $chainspec_name = $port8888data['chainspec_name'] ?? 'casper'; - $next_upgrade = $port8888data['next_upgrade']['protocol_version'] ?? ''; - - // record port 8888 data if available - $check = DB::select(" - SELECT public_key, era_id - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id = $current_era_id - "); - $check = $check[0] ?? null; - - if (!$check) { - DB::table('all_node_data')->insert( - array( - 'public_key' => $public_key, - 'era_id' => $current_era_id, - 'port8888_peers' => $peer_count, - 'port8888_era_id' => $era_id, - 'port8888_block_height' => $block_height, - 'port8888_build_version' => $build_version, - 'port8888_next_upgrade' => $next_upgrade - ) - ); - } else { - DB::table('all_node_data') - ->where('public_key', $public_key) - ->where('era_id', $current_era_id) - ->update( - array( - 'port8888_peers' => $peer_count, - 'port8888_era_id' => $era_id, - 'port8888_block_height' => $block_height, - 'port8888_build_version' => $build_version, - 'port8888_next_upgrade' => $next_upgrade - ) - ); - } - - break; - } - } - } - - // find MBS - info('Finding MBS for this era'); - rsort($MBS_arr); - $MBS = 0; - - if (count($MBS_arr) > 0) { - $MBS = (float)($MBS_arr[99] ?? $MBS_arr[count($MBS_arr) - 1]); - } - - // save MBS in new table by current_era - $check = DB::select(" - SELECT mbs - FROM mbs - WHERE era_id = $current_era_id - "); - $check = $check[0] ?? null; - - if (!$check) { - DB::table('mbs')->insert( - array( - 'era_id' => $current_era_id, - 'mbs' => $MBS, - 'created_at' => Carbon::now('UTC') - ) - ); - } else { - DB::table('mbs') - ->where('era_id', $current_era_id) - ->update( - array( - 'mbs' => $MBS, - 'updated_at' => Carbon::now('UTC') - ) - ); - } - - // Get settings for stable eras requirement - info('Getting settings'); - $voting_eras_to_vote = DB::select(" - SELECT value - FROM settings - WHERE name = 'voting_eras_to_vote' - "); - $a = (array)($voting_eras_to_vote[0] ?? array()); - $voting_eras_to_vote = (int)($a['value'] ?? 0); - - // Create setting if not exist - if (!$voting_eras_to_vote) { - $voting_eras_to_vote = 100; - - DB::table('settings')->insert( - array( - 'name' => 'voting_eras_to_vote', - 'value' => '100' - ) - ); - } - - // Discover black marks - validator has sufficient stake, but failed to win slot. - $uptime_calc_size = DB::select(" - SELECT value - FROM settings - WHERE name = 'uptime_calc_size' - "); - $a = (array)($uptime_calc_size[0] ?? array()); - $uptime_calc_size = (int)($a['value'] ?? 0); - - // Create setting if not exist - if (!$uptime_calc_size) { - $uptime_calc_size = 360; - - DB::table('settings')->insert( - array( - 'name' => 'uptime_calc_size', - 'value' => '360' - ) - ); - } - - $previous_era_id = $current_era_id - 1; - $previous_mbs = DB::select(" - SELECT mbs - FROM mbs - WHERE era_id = $previous_era_id - "); - $previous_mbs = (float)($previous_mbs[0]->mbs ?? 0); - - info('Looping auction era object again to find bad marks and historical performance'); - foreach ($bids as $b) { - $public_key = strtolower($b->public_key ?? 'nill'); - info('Complete: '.$public_key); - - // last era - // $previous_stake = DB::select(" - // SELECT bid_total_staked_amount - // FROM all_node_data - // WHERE public_key = '$public_key' - // AND era_id = $previous_era_id - // "); - // $a = (array)($previous_stake[0] ?? array()); - // $previous_stake = (int)($a['bid_total_staked_amount'] ?? 0); - - // current era - // $next_era_weight = DB::select(" - // SELECT next_era_weight - // FROM all_node_data - // WHERE public_key = '$public_key' - // AND era_id = $current_era_id - // "); - // $a = (array)($next_era_weight[0] ?? array()); - // $next_era_weight = (int)($a['next_era_weight'] ?? 0); - - - // last era and current era - $multi_query = DB::select(" - SELECT - era_id, bid_total_staked_amount, next_era_weight - FROM all_node_data - WHERE public_key = '$public_key' - AND ( - era_id = $previous_era_id OR - era_id = $current_era_id - ) - "); - - $previous_stake = 0; - $next_era_weight = 0; - - if ($multi_query) { - foreach ($multi_query as $result) { - if ($result->era_id == $previous_era_id) { - $previous_stake = (int)($result->bid_total_staked_amount); - } - - if ($result->era_id == $current_era_id) { - $next_era_weight = (int)($result->next_era_weight); - } - } - } - - - // Calculate historical_performance from past $uptime_calc_size eras - $missed = 0; - $in_curent_eras = DB::select(" - SELECT in_curent_era - FROM all_node_data - WHERE public_key = '$public_key' - ORDER BY era_id DESC - LIMIT $uptime_calc_size - "); - - $in_curent_eras = $in_curent_eras ? $in_curent_eras : array(); - $window = $in_curent_eras ? count($in_curent_eras) : 0; - - foreach ($in_curent_eras as $c) { - $in = (bool)($c->in_curent_era ?? 0); - - if (!$in) { - $missed += 1; - } - } - - // get node uptime from MAKE object - $uptime = 0; - - foreach ($global_uptime as $uptime_array) { - $fvid = strtolower($uptime_array->public_key ?? ''); - - if($fvid == $public_key) { - $uptime = (float)($uptime_array->average_score ?? 0); - break; - } - } - - $historical_performance = ($uptime * ($window - $missed)) / $window; - $past_era = $current_era_id - $voting_eras_to_vote; - $bad_mark = 0; - - if ($past_era < 0) { - $past_era = 0; - } - - if ( - $previous_stake > $previous_mbs && - !$next_era_weight - ) { - // black mark - $bad_mark = 1; - } - - // Update stability - $unstable = DB::select(" - SELECT era_id - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id > $past_era - AND ( - bad_mark = 1 OR - bid_inactive = 1 - ) - "); - - $stability_count = DB::select(" - SELECT era_id - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id > $past_era - "); - - $unstable = (bool)($unstable); - $stable = (int)(!$unstable); - $stability_count = $stability_count ? count($stability_count) : 0; - - if ($stability_count < $voting_eras_to_vote) { - $stable = 0; - } - - DB::table('all_node_data') - ->where('public_key', $public_key) - ->where('era_id', $current_era_id) - ->update( - array( - 'bad_mark' => $bad_mark, - 'stable' => $stable, - 'historical_performance' => $historical_performance - ) - ); - } - - // DailyEarning garbage cleanup - DailyEarning::where( - 'created_at', - '<', - Carbon::now('UTC')->subDays(90) - )->delete(); - - // end timer - $end_time = (int)time(); - - info("Time spent on era: ".($end_time - $start_time)); - } - - $historic_block += 1; - } - - $i += 1; - } - - */ info('done'); } } \ No newline at end of file From a5d762c2d51b28e92dd4ec8b81c41f047ea7aa6f Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 3 Nov 2022 11:21:54 -0400 Subject: [PATCH 091/162] historica data kernel --- app/Console/Kernel.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 1a1271b9..20d198d8 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -52,6 +52,12 @@ protected function schedule(Schedule $schedule) ->everyFifteenMinutes() ->runInBackground(); */ + + // New historical data getter + $schedule->command('historical-data') + ->everyFiveMinutes() + ->runInBackground(); + $schedule->command('refresh:address') ->everyFiveMinutes() ->runInBackground(); From 08822f89721a886b64f78879fb07bc88e9e8b3dc Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 3 Nov 2022 13:42:57 -0400 Subject: [PATCH 092/162] old nodehelp cleanup --- app/Console/Kernel.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 20d198d8..96bc1d98 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -55,6 +55,7 @@ protected function schedule(Schedule $schedule) // New historical data getter $schedule->command('historical-data') + ->withoutOverlapping() ->everyFiveMinutes() ->runInBackground(); From 7345d2fa492f948ab95397c5789fd3f823758b24 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 3 Nov 2022 13:43:25 -0400 Subject: [PATCH 093/162] Historic data withoutOverlapping --- app/Services/NodeHelper.php | 489 ------------------------------------ 1 file changed, 489 deletions(-) diff --git a/app/Services/NodeHelper.php b/app/Services/NodeHelper.php index 5c6888b6..37cb0832 100644 --- a/app/Services/NodeHelper.php +++ b/app/Services/NodeHelper.php @@ -341,495 +341,6 @@ public function getValidAddresses() return $addresses; } - /* - public function getValidatorStanding() - { - // get node ips from peers - // $port8888_responses = $this->discoverPeers(); - $port8888_responses = array(); - - // get auction state from trusted node RPC - $curl = curl_init(); - - $json_data = array( - 'id' => (int)time(), - 'jsonrpc' => '2.0', - 'method' => 'state_get_auction_info', - 'params' => array() - ); - - curl_setopt($curl, CURLOPT_URL, 'http://' . getenv('NODE_IP') . ':7777/rpc'); - curl_setopt($curl, CURLOPT_POST, true); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 15); - curl_setopt($curl, CURLOPT_TIMEOUT, 15); - curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($json_data)); - curl_setopt($curl, CURLOPT_HTTPHEADER, array( - 'Accept: application/json', - 'Content-type: application/json', - )); - - // parse response for bids - $auction_response = curl_exec($curl); - - if (curl_errno($curl) && getenv('BACKUP_NODE_IP')) { - curl_setopt($curl, CURLOPT_URL, 'http://' . getenv('BACKUP_NODE_IP') . ':7777/rpc'); - - // try twice with BACKUP_NODE_IP - $auction_response = curl_exec($curl); - } - - curl_close($curl); - - // very large object. aprx 10MB - $decoded_response = json_decode($auction_response, true); - $auction_state = $decoded_response['result']['auction_state'] ?? array(); - $bids = $auction_state['bids'] ?? array(); - - // get era ID - $era_validators = $auction_state['era_validators'] ?? array(); - $current_era_validators = $era_validators[0] ?? array(); - $next_era_validators = $era_validators[1] ?? array(); - $current_era_id = (int)($current_era_validators['era_id'] ?? 0); - $next_era_id = (int)($next_era_validators['era_id'] ?? 0); - - // loop current era - $current_validator_weights = $current_era_validators['validator_weights'] ?? array(); - - foreach ($current_validator_weights as $v) { - $public_key = $v['public_key'] ?? ''; - $weight = (int)($v['weight'] / 1000000000 ?? 0); - - $check = DB::select(" - SELECT public_key, era_id - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id = $current_era_id - "); - $check = $check[0] ?? null; - - if (!$check) { - DB::table('all_node_data')->insert( - array( - 'public_key' => $public_key, - 'era_id' => $current_era_id, - 'current_era_weight' => $weight, - 'in_curent_era' => 1, - 'created_at' => Carbon::now('UTC') - ) - ); - } - } - - // loop next era - $next_validator_weights = $next_era_validators['validator_weights'] ?? array(); - - foreach ($next_validator_weights as $v) { - $public_key = $v['public_key'] ?? ''; - $weight = (int)($v['weight'] / 1000000000 ?? 0); - - $check = DB::select(" - SELECT public_key, era_id - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id = $current_era_id - "); - $check = $check[0] ?? null; - - if (!$check) { - DB::table('all_node_data')->insert( - array( - 'public_key' => $public_key, - 'era_id' => $current_era_id, - 'next_era_weight' => $weight, - 'in_next_era' => 1 - ) - ); - } else { - DB::table('all_node_data') - ->where('public_key', $public_key) - ->where('era_id', $current_era_id) - ->update( - array( - 'next_era_weight' => $weight, - 'in_next_era' => 1 - ) - ); - } - } - - // set MBS array. minimum bid slot amount - $MBS_arr = array(); - - // get global uptimes from MAKE - $global_uptime = $this->retrieveGlobalUptime($current_era_id); - - // loop auction era - foreach ($bids as $b) { - $public_key = strtolower($b['public_key'] ?? 'nill'); - $bid = $b['bid'] ?? array(); - - // get self - $self_staked_amount = (int)($bid['staked_amount'] ?? 0); - $delegation_rate = (int)($bid['delegation_rate'] ?? 0); - $bid_inactive = (int)($bid['inactive'] ?? false); - - // calculate total stake, delegators + self stake - $delegators = (array)($bid['delegators'] ?? array()); - $delegators_count = count($delegators); - $delegators_staked_amount = 0; - - foreach ($delegators as $delegator) { - $delegators_staked_amount += (int)($delegator['staked_amount'] ?? 0); - } - - // convert and calculate stake amounts - $delegators_staked_amount = (int)($delegators_staked_amount / 1000000000); - $self_staked_amount = (int)($self_staked_amount / 1000000000); - $total_staked_amount = $delegators_staked_amount + $self_staked_amount; - - // append to MBS array and pluck 100th place later - $MBS_arr[$public_key] = $total_staked_amount; - - // get node uptime from MAKE object - $uptime = 0; - - foreach ($global_uptime as $uptime_array) { - $fvid = strtolower($uptime_array->public_key ?? ''); - - if($fvid == $public_key) { - $uptime = (float)($uptime_array->average_score ?? 0); - break; - } - } - - // record auction bid data for this node - $check = DB::select(" - SELECT public_key, era_id - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id = $current_era_id - "); - $check = $check[0] ?? null; - - if (!$check) { - DB::table('all_node_data')->insert( - array( - 'public_key' => $public_key, - 'era_id' => $current_era_id, - 'uptime' => $uptime, - 'in_auction' => 1, - 'bid_delegators_count' => $delegators_count, - 'bid_delegation_rate' => $delegation_rate, - 'bid_inactive' => $bid_inactive, - 'bid_self_staked_amount' => $self_staked_amount, - 'bid_delegators_staked_amount' => $delegators_staked_amount, - 'bid_total_staked_amount' => $total_staked_amount - ) - ); - } else { - DB::table('all_node_data') - ->where('public_key', $public_key) - ->where('era_id', $current_era_id) - ->update( - array( - 'uptime' => $uptime, - 'in_auction' => 1, - 'bid_delegators_count' => $delegators_count, - 'bid_delegation_rate' => $delegation_rate, - 'bid_inactive' => $bid_inactive, - 'bid_self_staked_amount' => $self_staked_amount, - 'bid_delegators_staked_amount' => $delegators_staked_amount, - 'bid_total_staked_amount' => $total_staked_amount - ) - ); - } - - // save current stake amount to daily earnings table - $earning = new DailyEarning(); - $earning->node_address = $public_key; - $earning->self_staked_amount = (int)$self_staked_amount; - $earning->created_at = Carbon::now('UTC'); - $earning->save(); - - // get difference between current self stake and yesterdays self stake - $get_earning = DailyEarning::where('node_address', $public_key) - ->where('created_at', '>', Carbon::now('UTC')->subHours(24)) - ->orderBy('created_at', 'asc') - ->first(); - - $yesterdays_self_staked_amount = (int)($get_earning->self_staked_amount ?? 0); - $daily_earning = $self_staked_amount - $yesterdays_self_staked_amount; - - // look for existing peer by public key for port 8888 data - foreach ($port8888_responses as $port8888data) { - $our_public_signing_key = strtolower($port8888data['our_public_signing_key'] ?? ''); - - if ($our_public_signing_key == $public_key) { - // found in peers - $peer_count = (int)($port8888data['peer_count'] ?? 0); - $era_id = (int)($port8888data['last_added_block_info']['era_id'] ?? 0); - $block_height = (int)($port8888data['last_added_block_info']['height'] ?? 0); - $build_version = $port8888data['build_version'] ?? '1.0.0'; - $build_version = explode('-', $build_version)[0]; - $chainspec_name = $port8888data['chainspec_name'] ?? 'casper'; - $next_upgrade = $port8888data['next_upgrade']['protocol_version'] ?? ''; - - // record port 8888 data if available - $check = DB::select(" - SELECT public_key, era_id - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id = $current_era_id - "); - $check = $check[0] ?? null; - - if (!$check) { - DB::table('all_node_data')->insert( - array( - 'public_key' => $public_key, - 'era_id' => $current_era_id, - 'port8888_peers' => $peer_count, - 'port8888_era_id' => $era_id, - 'port8888_block_height' => $block_height, - 'port8888_build_version' => $build_version, - 'port8888_next_upgrade' => $next_upgrade - ) - ); - } else { - DB::table('all_node_data') - ->where('public_key', $public_key) - ->where('era_id', $current_era_id) - ->update( - array( - 'port8888_peers' => $peer_count, - 'port8888_era_id' => $era_id, - 'port8888_block_height' => $block_height, - 'port8888_build_version' => $build_version, - 'port8888_next_upgrade' => $next_upgrade - ) - ); - } - - break; - } - } - } - - // find MBS - rsort($MBS_arr); - $MBS = 0; - - if (count($MBS_arr) > 0) { - $MBS = (float)($MBS_arr[99] ?? $MBS_arr[count($MBS_arr) - 1]); - } - - // save MBS in new table by current_era - $check = DB::select(" - SELECT mbs - FROM mbs - WHERE era_id = $current_era_id - "); - $check = $check[0] ?? null; - - if (!$check) { - DB::table('mbs')->insert( - array( - 'era_id' => $current_era_id, - 'mbs' => $MBS, - 'created_at' => Carbon::now('UTC') - ) - ); - } else { - DB::table('mbs') - ->where('era_id', $current_era_id) - ->update( - array( - 'mbs' => $MBS, - 'updated_at' => Carbon::now('UTC') - ) - ); - } - - // Get settings for stable eras requirement - $voting_eras_to_vote = DB::select(" - SELECT value - FROM settings - WHERE name = 'voting_eras_to_vote' - "); - $a = (array)($voting_eras_to_vote[0] ?? array()); - $voting_eras_to_vote = (int)($a['value'] ?? 0); - - // Create setting if not exist - if (!$voting_eras_to_vote) { - $voting_eras_to_vote = 100; - - DB::table('settings')->insert( - array( - 'name' => 'voting_eras_to_vote', - 'value' => '100' - ) - ); - } - - // Discover black marks - validator has sufficient stake, but failed to win slot. - $uptime_calc_size = DB::select(" - SELECT value - FROM settings - WHERE name = 'uptime_calc_size' - "); - $a = (array)($uptime_calc_size[0] ?? array()); - $uptime_calc_size = (int)($a['value'] ?? 0); - - // Create setting if not exist - if (!$uptime_calc_size) { - $uptime_calc_size = 360; - - DB::table('settings')->insert( - array( - 'name' => 'uptime_calc_size', - 'value' => '360' - ) - ); - } - - $previous_era_id = $current_era_id - 1; - $previous_mbs = DB::select(" - SELECT mbs - FROM mbs - WHERE era_id = $previous_era_id - "); - $previous_mbs = (float)($previous_mbs[0]->mbs ?? 0); - - foreach ($bids as $b) { - $public_key = strtolower($b['public_key'] ?? 'nill'); - - // last era - $previous_stake = DB::select(" - SELECT bid_total_staked_amount - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id = $previous_era_id - "); - $a = (array)($previous_stake[0] ?? array()); - $previous_stake = (int)($a['bid_total_staked_amount'] ?? 0); - - // current era - $next_era_weight = DB::select(" - SELECT next_era_weight - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id = $current_era_id - "); - $a = (array)($next_era_weight[0] ?? array()); - $next_era_weight = (int)($a['next_era_weight'] ?? 0); - - // Calculate historical_performance from past $uptime_calc_size eras - $missed = 0; - $in_curent_eras = DB::select(" - SELECT in_curent_era - FROM all_node_data - WHERE public_key = '$public_key' - ORDER BY era_id DESC - LIMIT $uptime_calc_size - "); - - $in_curent_eras = $in_curent_eras ? $in_curent_eras : array(); - $window = $in_curent_eras ? count($in_curent_eras) : 0; - - foreach ($in_curent_eras as $c) { - $a = (array)$c; - $in = (bool)($a['in_curent_era'] ?? 0); - - if (!$in) { - $missed += 1; - } - } - - // get node uptime from MAKE object - $uptime = 0; - - foreach ($global_uptime as $uptime_array) { - $fvid = strtolower($uptime_array->public_key ?? ''); - - if($fvid == $public_key) { - $uptime = (float)($uptime_array->average_score ?? 0); - break; - } - } - - $historical_performance = ($uptime * ($window - $missed)) / $window; - $past_era = $current_era_id - $voting_eras_to_vote; - $bad_mark = 0; - - if ($past_era < 0) { - $past_era = 0; - } - - if ( - $previous_stake > $previous_mbs && - !$next_era_weight - ) { - // black mark - $bad_mark = 1; - } - - // Update stability - $unstable = DB::select(" - SELECT public_key - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id > $past_era - AND ( - bad_mark = 1 OR - bid_inactive = 1 - ) - "); - - $stability_count = DB::select(" - SELECT public_key - FROM all_node_data - WHERE public_key = '$public_key' - AND era_id > $past_era - "); - - $unstable = (bool)($unstable); - $stable = (int)(!$unstable); - $stability_count = $stability_count ? count($stability_count) : 0; - - if ($stability_count < $voting_eras_to_vote) { - $stable = 0; - } - - DB::table('all_node_data') - ->where('public_key', $public_key) - ->where('era_id', $current_era_id) - ->update( - array( - 'bad_mark' => $bad_mark, - 'stable' => $stable, - 'historical_performance' => $historical_performance - ) - ); - } - - // DailyEarning garbage cleanup - DailyEarning::where('created_at', '<', Carbon::now('UTC')->subDays(90))->delete(); - - return true; - } - */ - - /* - public function getTotalRewards($validatorId) - { - $response = Http::withOptions([ - 'verify' => false, - ])->get("https://api.CSPR.live/validators/$validatorId/total-rewards"); - return $response->json(); - } - */ - public function getValidatorRewards($validatorId, $_range) { $nowtime = (int) time(); From f64ed26c629d62ca5c6afe6115993009d45b2a7f Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 3 Nov 2022 14:01:14 -0400 Subject: [PATCH 094/162] catch historic block overflow --- app/Console/Commands/HistoricalData.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index 8e0cc05e..0d7ab387 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -70,6 +70,12 @@ public function handle() $test_era = (int)($json->result->block->header->era_id ?? 0); $timestamp = $json->result->block->header->timestamp ?? ''; + if (!$timestamp) { + info('Historical block overflow - reset'); + $blocks_per_era = 101; + $historic_block = $blocks_per_era * $historic_era; + } + if ($test_era < $historic_era) { $era_diff = $historic_era - $test_era; From 44f7dbb9d490c56a6f8f7468acacc6c5db1622a4 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 3 Nov 2022 14:16:58 -0400 Subject: [PATCH 095/162] catch invalid historic block --- app/Console/Commands/HistoricalData.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index 0d7ab387..34d7e089 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -70,7 +70,7 @@ public function handle() $test_era = (int)($json->result->block->header->era_id ?? 0); $timestamp = $json->result->block->header->timestamp ?? ''; - if (!$timestamp) { + if (!$timestamp || $timestamp == '') { info('Historical block overflow - reset'); $blocks_per_era = 101; $historic_block = $blocks_per_era * $historic_era; From 1cdd42a932736226207170bf28d7ccf28dea4d87 Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 3 Nov 2022 14:27:11 -0400 Subject: [PATCH 096/162] historic data bug fix --- app/Console/Commands/HistoricalData.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index 34d7e089..a7627b13 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -74,6 +74,7 @@ public function handle() info('Historical block overflow - reset'); $blocks_per_era = 101; $historic_block = $blocks_per_era * $historic_era; + break; } if ($test_era < $historic_era) { From 3fecbce9134431f47c1a3e05ca624f7a62a7441b Mon Sep 17 00:00:00 2001 From: = <=> Date: Thu, 3 Nov 2022 14:54:41 -0400 Subject: [PATCH 097/162] controller bug fixes --- app/Http/Controllers/Api/V1/AdminController.php | 2 +- app/Http/Controllers/Api/V1/UserController.php | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index e8b84fa7..1d708212 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -1915,7 +1915,7 @@ public function getVerificationDetail($id) try { $declined_reason = json_decode(json_decode($user->data))->declined_reason; - } catch (Exception $e) {} + } catch (\Exception $ex) {} $user->declined_reason = $declined_reason; diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 1d2517b4..104970cc 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1759,6 +1759,8 @@ public function vote($id, Request $request) $voting_eras_since_redmark = $voting_eras_since_redmark[0] ?? array(); $voting_eras_since_redmark = (int)($voting_eras_since_redmark->value ?? 0); + $current_era_id = Helper::getCurrentERAId(); + foreach ($addresses as $address) { $p = $address->public_address_node ?? ''; From 60989a031bda5e07c67b9919d11f8289d0d6680c Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 7 Nov 2022 11:29:07 -0500 Subject: [PATCH 098/162] daily earning change table --- app/Services/NodeHelper.php | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/app/Services/NodeHelper.php b/app/Services/NodeHelper.php index 37cb0832..956b45c8 100644 --- a/app/Services/NodeHelper.php +++ b/app/Services/NodeHelper.php @@ -410,9 +410,16 @@ public function getValidatorRewards($validatorId, $_range) } $timestamp = Carbon::createFromTimestamp($range, 'UTC')->toDateTimeString(); - $total_records = DailyEarning::where('node_address', $validatorId) - ->where('created_at', '>', $timestamp) - ->get(); + // $total_records = DailyEarning::where('node_address', $validatorId) + // ->where('created_at', '>', $timestamp) + // ->get(); + + $total_records = DB::select(" + SELECT bid_self_staked_amount + FROM all_node_data2 + WHERE created_at > '$timestamp' + AND public_key = '$validatorId' + "); $new_array = []; $display_record_count = 100; @@ -424,7 +431,7 @@ public function getValidatorRewards($validatorId, $_range) $new_array = []; $i = $modulo; - if ($modulo == 0) { + if ((int)$modulo == 0) { $modulo = 1; } From 9b584f574691f2ab7e1b5d9711bfa872f7b75192 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 7 Nov 2022 11:36:02 -0500 Subject: [PATCH 099/162] add created_at to daily earning request --- app/Services/NodeHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/NodeHelper.php b/app/Services/NodeHelper.php index 956b45c8..20fad073 100644 --- a/app/Services/NodeHelper.php +++ b/app/Services/NodeHelper.php @@ -415,7 +415,7 @@ public function getValidatorRewards($validatorId, $_range) // ->get(); $total_records = DB::select(" - SELECT bid_self_staked_amount + SELECT bid_self_staked_amount, created_at FROM all_node_data2 WHERE created_at > '$timestamp' AND public_key = '$validatorId' From 9fb153cb766fa5e6b16775d2a98fd6e3576eb1ca Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 7 Nov 2022 11:41:09 -0500 Subject: [PATCH 100/162] change self_staked_amount -> bid_self_staked_amount --- app/Services/NodeHelper.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Services/NodeHelper.php b/app/Services/NodeHelper.php index 20fad073..d6cd9719 100644 --- a/app/Services/NodeHelper.php +++ b/app/Services/NodeHelper.php @@ -437,7 +437,7 @@ public function getValidatorRewards($validatorId, $_range) foreach ($total_records as $record) { if ($i % $modulo == 0) { - $new_array[(string) strtotime($record->created_at.' UTC')] = (string) $record->self_staked_amount; + $new_array[(string) strtotime($record->created_at.' UTC')] = (string) $record->bid_self_staked_amount; } $i++; } From 43a1c017d4092ac83d3bde1a0a45215bac764666 Mon Sep 17 00:00:00 2001 From: = <=> Date: Mon, 7 Nov 2022 13:14:13 -0500 Subject: [PATCH 101/162] voting logic fix --- .../Controllers/Api/V1/UserController.php | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 104970cc..47186f7e 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1680,27 +1680,26 @@ public function canVote() ORDER BY era_id DESC LIMIT 1 "); - if (!$temp) $temp = []; - $good_standing_eras = 0; - if (isset($temp[0])) $good_standing_eras = (int) ($temp[0]->era_id ?? 0); - if ($current_era_id > $good_standing_eras) $good_standing_eras = $current_era_id - $good_standing_eras; + if (!$temp) { + $temp = []; + } + + $good_standing_eras = $current_era_id - (int) ($temp[0]->era_id ?? 0); + if ($good_standing_eras > $return['good_standing_eras']) { $return['good_standing_eras'] = $good_standing_eras; } // total_active_eras - $temp = DB::select(" + $total_active_eras = DB::select(" SELECT count(id) as tCount FROM all_node_data2 WHERE public_key = '$p' "); - if (!$temp) $temp = []; - $eras = 0; - if (isset($temp[0])) $eras = (int) ($temp[0]->tCount ?? 0); - if ($current_era_id - $eras > $return['total_active_eras']) { - $return['total_active_eras'] = $current_era_id - $eras; - } + + $total_active_eras = $total_active_eras[0] ?? array(); + $return['total_active_eras'] = (int)($total_active_eras->tCount ?? 0); if ( $return['total_active_eras'] >= $voting_eras_to_vote && @@ -1787,14 +1786,13 @@ public function vote($id, Request $request) // total_active_eras $total_active_eras = DB::select(" - SELECT count(id) + SELECT count(id) AS tCount FROM all_node_data2 WHERE public_key = '$p' "); - $total_active_eras = (array)($total_active_eras[0] ?? array()); - $total_active_eras = (int)($total_active_eras['count(id)'] ?? 0); - $total_active_eras = $current_era_id - $total_active_eras; + $total_active_eras = $total_active_eras[0] ?? array(); + $total_active_eras = (int)($total_active_eras->tCount ?? 0); if ( $total_active_eras >= $voting_eras_to_vote && From 58fd27efd14d74ceb45ed655acdd178a53b09947 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 8 Nov 2022 10:31:46 -0500 Subject: [PATCH 102/162] ballot start time adjust --- .../Controllers/Api/V1/AdminController.php | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 1d708212..2d83b244 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -1045,15 +1045,15 @@ public function editBallot($id, Request $request) DB::beginTransaction(); // Validator $validator = Validator::make($request->all(), [ - 'title' => 'nullable', - 'description' => 'nullable', - 'files' => 'array', - 'files.*' => 'file|max:10240|mimes:pdf,docx,doc,txt,rtf', + 'title' => 'nullable', + 'description' => 'nullable', + 'files' => 'array', + 'files.*' => 'file|max:10240|mimes:pdf,docx,doc,txt,rtf', 'file_ids_remove' => 'array', - 'start_date' => 'required', - 'start_time' => 'required', - 'end_date' => 'required', - 'end_time' => 'required', + 'start_date' => 'required', + 'start_time' => 'required', + 'end_date' => 'required', + 'end_time' => 'required', ]); if ($validator->fails()) { @@ -1076,9 +1076,13 @@ public function editBallot($id, Request $request) $ballot->description = $request->description; } + $endTime = $request->end_date . ' ' . $request->end_time; + $endTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $endTime, 'EST'); + $endTimeCarbon->setTimezone('UTC'); + $now = Carbon::now('UTC'); $ballot->created_at = $now; - $ballot->time_end = $request->end_date . ' ' . $request->end_time; + $ballot->time_end = $endTimeCarbon; $ballot->start_date = $request->start_date; $ballot->start_time = $request->start_time; $ballot->end_date = $request->end_date; @@ -1213,7 +1217,7 @@ public function cancelBallot($id) if (!$ballot || $ballot->status != 'active') { return $this->errorResponse( - 'Cannot cancle ballot', + 'Cannot cancel ballot', Response::HTTP_BAD_REQUEST ); } @@ -1266,12 +1270,12 @@ public function getGlobalSettings() $data = [ 'globalSettings' => $settings, - 'lockRules' => [ + 'lockRules' => [ 'kyc_not_verify' => $ruleKycNotVerify, 'status_is_poor' => $ruleStatusIsPoor ], 'membershipAgreementFile' => $membershipAgreementFile, - 'contactRecipients' => $contactRecipients + 'contactRecipients' => $contactRecipients ]; return $this->successResponse($data); @@ -1285,12 +1289,12 @@ public function updateGlobalSettings(Request $request) 'uptime_warning' => ($request->uptime_warning ?? null), 'uptime_probation' => ($request->uptime_probation ?? null), 'uptime_correction_unit' => ($request->uptime_correction_unit ?? null), - 'uptime_correction_value' => ($request->uptime_correction_value ?? null), + 'uptime_correction_value' => ($request->uptime_correction_value ?? null), 'uptime_calc_size' => ($request->uptime_calc_size ?? null), 'voting_eras_to_vote' => ($request->voting_eras_to_vote ?? null), 'voting_eras_since_redmark' => ($request->voting_eras_since_redmark ?? null), 'redmarks_revoke' => ($request->redmarks_revoke ?? null), - 'redmarks_revoke_calc_size' => ($request->redmarks_revoke_calc_size ?? null), + 'redmarks_revoke_calc_size' => ($request->redmarks_revoke_calc_size ?? null), 'responsiveness_warning' => ($request->responsiveness_warning ?? null), 'responsiveness_probation' => ($request->responsiveness_probation ?? null) ]; @@ -1304,7 +1308,7 @@ public function updateGlobalSettings(Request $request) $setting->save(); } else { $setting = new Setting(); - $setting->name = $name; + $setting->name = $name; $setting->value = $value; $setting->save(); } From db09a981695a764859c07b7d1f77213abf41b622 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Tue, 8 Nov 2022 10:33:56 -0500 Subject: [PATCH 103/162] # Sorting --- app/Http/Controllers/Api/V1/AdminController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 1d708212..df885253 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -534,7 +534,7 @@ public function getUsers(Request $request) } $query .= " ORDER BY " . $sort_key . " " . $sort_direction; } else { - $query .= " ORDER BY a.id asc"; + $query .= " ORDER BY a.id desc"; } $users = DB::select($query); From d8f2c1637a53ecbf9f90d85b9d870272fa97128e Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 8 Nov 2022 11:45:53 -0500 Subject: [PATCH 104/162] missing red marks logic --- app/Console/Commands/CheckNodeStatus.php | 39 +++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index 65bbe49a..a32af0b2 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -101,7 +101,11 @@ public function handle() $address->save(); $hasOnline = true; - if (isset($settings['uptime_probation']) && (float) $settings['uptime_probation'] > 0) { + // historical performance + if ( + isset($settings['uptime_probation']) && + (float) $settings['uptime_probation'] > 0 + ) { $uptime_calc_size = $settings['uptime_calc_size'] ?? 1; $uptime_calc_size = (int) $uptime_calc_size; @@ -132,6 +136,39 @@ public function handle() $hasOnProbation = true; } } + + // redmarks + if ( + isset($settings['redmarks_revoke']) && + (float) $settings['redmarks_revoke'] > 0 + ) { + $redmarks_revoke_calc_size = (int)($settings['redmarks_revoke_calc_size'] ?? 1); + $window = $current_era_id - $redmarks_revoke_calc_size; + + if ($window < 0) { + $window = 0; + } + + $bad_marks = DB::select(" + SELECT count(era_id) AS bad_marks + FROM all_node_data2 + WHERE public_key = '$public_address_node' + AND era_id > $window + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) + ORDER BY era_id DESC + "); + $bad_marks = $bad_marks[0] ?? []; + $bad_marks = (int)($bad_marks->bad_marks ?? 0); + + if ($bad_marks > $settings['redmarks_revoke']) { + $address->extra_status = 'On Probation'; + $address->save(); + $hasOnProbation = true; + } + } } else { $address->node_status = 'Offline'; $address->extra_status = null; From e72e454233726e96b422aaefe278f4490c1b2f94 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 8 Nov 2022 12:06:09 -0500 Subject: [PATCH 105/162] rm redmarks order by statement --- app/Console/Commands/CheckNodeStatus.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index a32af0b2..b06e4b4f 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -158,7 +158,6 @@ public function handle() in_current_era = 0 OR bid_inactive = 1 ) - ORDER BY era_id DESC "); $bad_marks = $bad_marks[0] ?? []; $bad_marks = (int)($bad_marks->bad_marks ?? 0); From 09f123a08162875ca46c25d43e8dacdbc1cf6de5 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 8 Nov 2022 12:36:37 -0500 Subject: [PATCH 106/162] suspend on too many bad marks --- app/Console/Commands/CheckNodeStatus.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index b06e4b4f..8ec01c17 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -163,7 +163,7 @@ public function handle() $bad_marks = (int)($bad_marks->bad_marks ?? 0); if ($bad_marks > $settings['redmarks_revoke']) { - $address->extra_status = 'On Probation'; + $address->extra_status = 'Suspended'; $address->save(); $hasOnProbation = true; } From af531688463643146a68bb3cf46ad5a9a67da7c8 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 8 Nov 2022 13:05:01 -0500 Subject: [PATCH 107/162] more suspended logic --- app/Console/Commands/CheckNodeStatus.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index 8ec01c17..36e294e6 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -162,8 +162,10 @@ public function handle() $bad_marks = $bad_marks[0] ?? []; $bad_marks = (int)($bad_marks->bad_marks ?? 0); - if ($bad_marks > $settings['redmarks_revoke']) { + if ($bad_marks > (int)$settings['redmarks_revoke']) { + $user->node_status = 'Offline'; $address->extra_status = 'Suspended'; + $address->node_status = 'Offline'; $address->save(); $hasOnProbation = true; } From b474b893933c84228edb51be10d96735cfc9e889 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 8 Nov 2022 13:18:56 -0500 Subject: [PATCH 108/162] check node -> user suspended --- app/Console/Commands/CheckNodeStatus.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index 36e294e6..88851bcf 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -99,7 +99,6 @@ public function handle() $address->node_status = 'Online'; $address->extra_status = null; $address->save(); - $hasOnline = true; // historical performance if ( @@ -168,6 +167,8 @@ public function handle() $address->node_status = 'Offline'; $address->save(); $hasOnProbation = true; + } else { + $hasOnline = true; } } } else { From a361b7f3250e5cda96265390b25bf7b26c193afe Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 8 Nov 2022 13:25:42 -0500 Subject: [PATCH 109/162] kjsfkjfs --- app/Console/Commands/CheckNodeStatus.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index 88851bcf..f41a49a8 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -166,7 +166,6 @@ public function handle() $address->extra_status = 'Suspended'; $address->node_status = 'Offline'; $address->save(); - $hasOnProbation = true; } else { $hasOnline = true; } From dfc2e53052a19c09f66e2a85a6acc2851041396b Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 8 Nov 2022 15:51:12 -0500 Subject: [PATCH 110/162] cant vote with too many redmarks in a given period of time --- .../Controllers/Api/V1/UserController.php | 67 +++++++++++++++++-- 1 file changed, 62 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 47186f7e..b6d31794 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1652,8 +1652,23 @@ public function canVote() $current_era_id = Helper::getCurrentERAId(); $settings = Helper::getSettings(); - $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; - $voting_eras_since_redmark = isset($settings['voting_eras_since_redmark']) ? (int) $settings['voting_eras_since_redmark'] : 1; + $voting_eras_to_vote = + isset($settings['voting_eras_to_vote']) ? + (int) $settings['voting_eras_to_vote'] : + 1; + + $voting_eras_since_redmark = + isset($settings['voting_eras_since_redmark']) ? + (int) $settings['voting_eras_since_redmark'] : + 1; + + $redmarks_revoke = (int)($settings['redmarks_revoke'] ?? 1); + $redmarks_revoke_calc_size = (int)($settings['redmarks_revoke_calc_size'] ?? 1); + $window = $current_era_id - $redmarks_revoke_calc_size; + + if ($window < 0) { + $window = 0; + } $return['setting_voting_eras'] = $voting_eras_to_vote; $return['setting_good_standing_eras'] = $voting_eras_since_redmark; @@ -1696,14 +1711,31 @@ public function canVote() SELECT count(id) as tCount FROM all_node_data2 WHERE public_key = '$p' + AND in_current_era = 1 + AND bid_inactive = 0 "); $total_active_eras = $total_active_eras[0] ?? array(); $return['total_active_eras'] = (int)($total_active_eras->tCount ?? 0); + // redmarks + $bad_marks = DB::select(" + SELECT count(era_id) AS bad_marks + FROM all_node_data2 + WHERE public_key = '$public_address_node' + AND era_id > $window + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) + "); + $bad_marks = $bad_marks[0] ?? []; + $bad_marks = (int)($bad_marks->bad_marks ?? 0); + if ( $return['total_active_eras'] >= $voting_eras_to_vote && - $return['good_standing_eras'] >= $voting_eras_since_redmark + $return['good_standing_eras'] >= $voting_eras_since_redmark && + $bad_marks > $redmarks_revoke ) { $return['can_vote'] = true; } @@ -1741,6 +1773,8 @@ public function vote($id, Request $request) $addresses = array(); } + $current_era_id = Helper::getCurrentERAId(); + // get settings $voting_eras_to_vote = DB::select(" SELECT value @@ -1758,7 +1792,13 @@ public function vote($id, Request $request) $voting_eras_since_redmark = $voting_eras_since_redmark[0] ?? array(); $voting_eras_since_redmark = (int)($voting_eras_since_redmark->value ?? 0); - $current_era_id = Helper::getCurrentERAId(); + $redmarks_revoke = (int)($settings['redmarks_revoke'] ?? 1); + $redmarks_revoke_calc_size = (int)($settings['redmarks_revoke_calc_size'] ?? 1); + $window = $current_era_id - $redmarks_revoke_calc_size; + + if ($window < 0) { + $window = 0; + } foreach ($addresses as $address) { $p = $address->public_address_node ?? ''; @@ -1789,14 +1829,31 @@ public function vote($id, Request $request) SELECT count(id) AS tCount FROM all_node_data2 WHERE public_key = '$p' + AND in_current_era = 1 + AND bid_inactive = 0 "); $total_active_eras = $total_active_eras[0] ?? array(); $total_active_eras = (int)($total_active_eras->tCount ?? 0); + // redmarks + $bad_marks = DB::select(" + SELECT count(era_id) AS bad_marks + FROM all_node_data2 + WHERE public_key = '$public_address_node' + AND era_id > $window + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) + "); + $bad_marks = $bad_marks[0] ?? []; + $bad_marks = (int)($bad_marks->bad_marks ?? 0); + if ( $total_active_eras >= $voting_eras_to_vote && - $good_standing_eras >= $voting_eras_since_redmark + $good_standing_eras >= $voting_eras_since_redmark && + $bad_marks > $redmarks_revoke ) { $stable = true; } From 3f4bd53e38fbdea985efabb5e37631a49ccd3184 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 8 Nov 2022 15:56:29 -0500 Subject: [PATCH 111/162] node sttaus revert to old logic --- app/Console/Commands/CheckNodeStatus.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index f41a49a8..cc2f4168 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -96,6 +96,7 @@ public function handle() ->where('in_current_era', 1) ->first(); if ($temp) { + $hasOnline = true; $address->node_status = 'Online'; $address->extra_status = null; $address->save(); @@ -162,12 +163,8 @@ public function handle() $bad_marks = (int)($bad_marks->bad_marks ?? 0); if ($bad_marks > (int)$settings['redmarks_revoke']) { - $user->node_status = 'Offline'; $address->extra_status = 'Suspended'; - $address->node_status = 'Offline'; $address->save(); - } else { - $hasOnline = true; } } } else { From 67e7a8d7e4307b16d701adb673e42cea8d085592 Mon Sep 17 00:00:00 2001 From: = <=> Date: Tue, 8 Nov 2022 16:07:27 -0500 Subject: [PATCH 112/162] p typo --- app/Http/Controllers/Api/V1/UserController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index b6d31794..3233ca66 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1722,7 +1722,7 @@ public function canVote() $bad_marks = DB::select(" SELECT count(era_id) AS bad_marks FROM all_node_data2 - WHERE public_key = '$public_address_node' + WHERE public_key = '$p' AND era_id > $window AND ( in_current_era = 0 OR @@ -1840,7 +1840,7 @@ public function vote($id, Request $request) $bad_marks = DB::select(" SELECT count(era_id) AS bad_marks FROM all_node_data2 - WHERE public_key = '$public_address_node' + WHERE public_key = '$p' AND era_id > $window AND ( in_current_era = 0 OR From ddad435186515d72a927b23de35f261cdc4843c2 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 9 Nov 2022 22:20:31 -0500 Subject: [PATCH 113/162] # Node Status --- app/Console/Commands/CheckNodeStatus.php | 414 ++++-------------- app/Console/Helper.php | 67 ++- app/Console/Kernel.php | 1 + .../Controllers/Api/V1/UserController.php | 50 +-- ...0_014252_update_user_addresses_6_table.php | 21 + 5 files changed, 172 insertions(+), 381 deletions(-) create mode 100644 database/migrations/2022_11_10_014252_update_user_addresses_6_table.php diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index cc2f4168..dbc9c181 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -18,39 +18,17 @@ class CheckNodeStatus extends Command { - /** - * The name and signature of the console command. - * - * @var string - */ protected $signature = 'node-status:check'; - /** - * The console command description. - * - * @var string - */ protected $description = 'Check node status'; - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { + public function __construct() { parent::__construct(); } - /** - * Execute the console command. - * - * @return int - */ - public function handle() - { + public function handle() { $settings = Helper::getSettings(); - $current_era_id = Helper::getCurrentERAId(); + $current_era_id = (int) ($settings['current_era_id'] ?? 0); $now = Carbon::now('UTC'); $users = User::with(['addresses', 'profile']) @@ -58,33 +36,37 @@ public function handle() ->where('banned', 0) ->get(); + $uptimeHours = 0; + if (isset($settings['uptime_correction_unit']) && isset($settings['uptime_correction_value'])) { + $unit = $settings['uptime_correction_unit']; + $value = (float) $settings['uptime_correction_value']; + + if ($unit == 'Weeks') { + $uptimeHours = $value * 7 * 24; + } else if ($unit == 'Days') { + $uptimeHours = $value * 24; + } else { + $uptimeHours = $value; + } + } + foreach ($users as $user) { $addresses = $user->addresses ?? []; if ( - !$addresses || - count($addresses) == 0 || - !$user->node_verified_at || - !$user->letter_verified_at || - !$user->signature_request_id + $addresses && + count($addresses) > 0 && + $user->node_verified_at && + $user->letter_verified_at && + $user->signature_request_id && + isset($user->profile) && + $user->profile && + $user->profile->status == 'approved' ) { - $user->node_status = null; - $user->save(); + // Verified Users - if ($user->profile) { - $user->profile->extra_status = null; - $user->profile->save(); - } - - if ($addresses && count($addresses) > 0) { - foreach ($addresses as $address) { - $address->node_status = null; - $address->extra_status = null; - $address->save(); - } - } - } else { - $hasOnline = $hasOnProbation = false; + $hasOnline = $hasOnProbation = $hasNotSuspended = false; + foreach ($addresses as $address) { $public_address_node = strtolower($address->public_address_node); @@ -96,80 +78,67 @@ public function handle() ->where('in_current_era', 1) ->first(); if ($temp) { + // All Node Data2 Record Exists! + $hasOnline = true; $address->node_status = 'Online'; $address->extra_status = null; $address->save(); - // historical performance + // Historical Performance if ( isset($settings['uptime_probation']) && (float) $settings['uptime_probation'] > 0 ) { - $uptime_calc_size = $settings['uptime_calc_size'] ?? 1; - $uptime_calc_size = (int) $uptime_calc_size; - - $uptime = (float) $temp->uptime; - if ($uptime_calc_size > 0) { - $missed = 0; - $in_current_eras = DB::select(" - SELECT in_current_era - FROM all_node_data2 - WHERE public_key = '$public_address_node' - ORDER BY era_id DESC - LIMIT $uptime_calc_size - "); - $in_current_eras = $in_current_eras ? $in_current_eras : []; - $window = $in_current_eras ? count($in_current_eras) : 0; - foreach ($in_current_eras as $c) { - $in = (bool) ($c->in_current_era ?? 0); - if (!$in) { - $missed += 1; - } - } - $uptime = round((float) (($uptime * ($window - $missed)) / $window), 2); - } + $uptime = Helper::calculateUptime($temp, $public_address_node, $settings); if ($uptime < (float) $settings['uptime_probation']) { $address->extra_status = 'On Probation'; + if (!$address->probation_start || !$address->probation_end) { + $address->probation_start = now(); + $address->probation_end = Carbon::now('UTC')->addHours($uptimeHours); + } + $address->save(); + + $now = Carbon::now('UTC'); + if ($address->probation_end <= $now) { + $address->extra_status = 'Suspended'; + $address->probation_start = null; + $address->probation_end = null; + $address->save(); + } else { + $hasOnProbation = true; + } + } else { + $address->probation_start = null; + $address->probation_end = null; $address->save(); - $hasOnProbation = true; } } - // redmarks + // Redmarks if ( isset($settings['redmarks_revoke']) && - (float) $settings['redmarks_revoke'] > 0 + (int) $settings['redmarks_revoke'] > 0 ) { - $redmarks_revoke_calc_size = (int)($settings['redmarks_revoke_calc_size'] ?? 1); - $window = $current_era_id - $redmarks_revoke_calc_size; - - if ($window < 0) { - $window = 0; - } - - $bad_marks = DB::select(" - SELECT count(era_id) AS bad_marks - FROM all_node_data2 - WHERE public_key = '$public_address_node' - AND era_id > $window - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - "); - $bad_marks = $bad_marks[0] ?? []; - $bad_marks = (int)($bad_marks->bad_marks ?? 0); - - if ($bad_marks > (int)$settings['redmarks_revoke']) { + $bad_marks = Helper::calculateBadMarks($temp, $public_address_node, $settings); + + if ($bad_marks > (int) $settings['redmarks_revoke']) { $address->extra_status = 'Suspended'; + $address->probation_start = null; + $address->probation_end = null; $address->save(); + } else { + $hasNotSuspended = true; } } } else { + // All Node Data2 Record Doesn't Exist! + $address->node_status = 'Offline'; $address->extra_status = null; + $address->probation_start = null; + $address->probation_end = null; $address->save(); } } @@ -182,248 +151,37 @@ public function handle() $user->save(); } - if ($user->profile) { - if ($hasOnProbation) { - $user->profile->extra_status = 'On Probation'; - $user->profile->save(); - } else { - $user->profile->extra_status = null; - $user->profile->save(); - } + if ($hasOnProbation) { + $user->profile->extra_status = 'On Probation'; + $user->profile->save(); + } else { + $user->profile->extra_status = null; + $user->profile->save(); } - } - } - } - - public function handleOld() - { - $uptime = MonitoringCriteria::where('type', 'uptime')->first(); - $uptimeProbationStart = $uptime->probation_start; - if ($uptime->given_to_correct_unit == 'Weeks') { - $uptimeTime = (float) $uptime->given_to_correct_value * 7 * 24; - } else if ($uptime->given_to_correct_unit == 'Days') { - $uptimeTime = (float) $uptime->given_to_correct_value * 24; - } else { - $uptimeTime = (float) $uptime->given_to_correct_value; - } - - $blockHeight = MonitoringCriteria::where('type', 'block-height')->first(); - $blockHeightProbationStart = (float) $blockHeight->probation_start; - if ($blockHeight->given_to_correct_unit == 'Weeks') { - $blockHeightTime = (float) $blockHeight->given_to_correct_value * 7 * 24; - } else if ($blockHeight->given_to_correct_unit == 'Days') { - $blockHeightTime = (float) $blockHeight->given_to_correct_value * 24; - } else { - $blockHeightTime = (float) $blockHeight->given_to_correct_value; - } - - $updateResponsiveness = MonitoringCriteria::where('type', 'update-responsiveness')->first(); - $updateResponsivenessProbationStart = (float) $updateResponsiveness->probation_start; - if ($updateResponsiveness->given_to_correct_unit == 'Weeks') { - $updateResponsivenessTime = (float) $updateResponsiveness->given_to_correct_value * 7 * 24; - } else if ($updateResponsiveness->given_to_correct_unit == 'Days') { - $updateResponsivenessTime = (float) $updateResponsiveness->given_to_correct_value * 24; - } else { - $updateResponsivenessTime = (float) $updateResponsiveness->given_to_correct_value; - } - - $now = Carbon::now('UTC'); - $users = User::with(['addresses', 'metric', 'nodeInfo', 'profile']) - ->where('role', 'member') - ->where('banned', 0) - ->get(); - - foreach ($users as $user) { - $user->node_status = 'Online'; - $user->save(); - $addresses = $user->addresses ?? []; - $nodeInfo = $user->nodeInfo ? $user->nodeInfo : $user->metric; + if ($hasOnline && !$hasNotSuspended) { + $user->profile->extra_status = 'Suspended'; + $user->profile->save(); + } + } else { + // Non-Verified Users - if ( - !$nodeInfo || - !$addresses || - count($addresses) == 0 || - !$user->node_verified_at || - !$user->letter_verified_at || - !$user->signature_request_id - ) { $user->node_status = null; $user->save(); - } else { - $hasNotFailNode = false; - foreach ($addresses as $address) { - $public_address_node = strtolower($address->public_address_node); - $nodeInfoAddress = NodeInfo::where('node_address', $public_address_node)->first(); - if (!$nodeInfoAddress) { - $address->node_status = null; - $address->save(); - } - if ($address->is_fail_node != 1) { - $hasNotFailNode = true; - } - } - - if (!$hasNotFailNode) { - $user->node_status = 'Offline'; - $user->save(); - } else { - // Begin For Each - foreach ($addresses as $userAddress) { - $public_address_node = strtolower($userAddress->public_address_node); - $nodeInfoAddress = NodeInfo::where('node_address', $public_address_node)->first(); - - if ($nodeInfoAddress) { - $nodeInfoAddress->uptime = $nodeInfoAddress->uptime ? $nodeInfoAddress->uptime : 0; - $nodeInfoAddress->block_height_average = $nodeInfoAddress->block_height_average ? $nodeInfoAddress->block_height_average : 0; - $nodeInfoAddress->update_responsiveness = $nodeInfoAddress->update_responsiveness ? $nodeInfoAddress->update_responsiveness : 100; - if ( - $nodeInfoAddress->uptime >= $uptimeProbationStart && - $nodeInfoAddress->block_height_average >= $blockHeightProbationStart && - $nodeInfoAddress->update_responsiveness >= $updateResponsivenessProbationStart - ) { - $userAddress->node_status = 'Online'; - $userAddress->save(); - - $nodeInfoAddress->uptime_time_start = null; - $nodeInfoAddress->uptime_time_end = null; - - $nodeInfoAddress->block_height_average_time_start = null; - $nodeInfoAddress->block_height_average_time_end = null; - - $nodeInfoAddress->update_responsiveness_time_start = null; - $nodeInfoAddress->update_responsiveness_time_end = null; - - $nodeInfoAddress->save(); - } - } - - if ( - isset($user->profile) && - $user->profile && - isset($user->profile->status) && - $user->profile->status == 'approved' && - $nodeInfoAddress - ) { - $userAddress->extra_status = null; - $userAddress->save(); - - if ($nodeInfoAddress->uptime < $uptimeProbationStart) { - $userAddress->extra_status = 'On Probation'; - - if(!$nodeInfoAddress->uptime_time_start) { - $nodeInfoAddress->uptime_time_start = now(); - } - - if(!$nodeInfoAddress->uptime_time_end) { - $nodeInfoAddress->uptime_time_end = Carbon::now('UTC')->addHours($uptimeTime); - } - } - - if ($nodeInfoAddress->block_height_average < $blockHeightProbationStart) { - $userAddress->extra_status = 'On Probation'; - - if(!$nodeInfoAddress->block_height_average_time_start) { - $nodeInfoAddress->block_height_average_time_start = now(); - } - - if(!$nodeInfoAddress->block_height_average_time_end) { - $nodeInfoAddress->block_height_average_time_end = Carbon::now('UTC')->addHours($blockHeightTime); - } - } - - if ($nodeInfoAddress->update_responsiveness < $updateResponsivenessProbationStart) { - $userAddress->extra_status = 'On Probation'; - - if(!$nodeInfoAddress->update_responsiveness_time_start) { - $nodeInfoAddress->update_responsiveness_time_start = now(); - } - - if(!$nodeInfoAddress->update_responsiveness_time_end) { - $nodeInfoAddress->update_responsiveness_time_end = Carbon::now('UTC')->addHours($updateResponsivenessTime); - } - } - $userAddress->save(); - $nodeInfoAddress->save(); - - if ($userAddress->extra_status == 'On Probation') { - if ($nodeInfoAddress->uptime_time_end <= $now && $nodeInfoAddress->uptime < $uptimeProbationStart) { - $userAddress->extra_status = 'Suspended'; - } - if ($nodeInfoAddress->block_height_average_time_end <= $now && $nodeInfoAddress->block_height_average < $blockHeightProbationStart) { - $userAddress->extra_status = 'Suspended'; - } - if ($nodeInfoAddress->update_responsiveness_time_end <= $now && $nodeInfoAddress->update_responsiveness < $updateResponsivenessProbationStart) { - $userAddress->extra_status = 'Suspended'; - } - $userAddress->save(); - } - } - - $inactive = (bool)($nodeInfoAddress->inactive ?? false); - - if($inactive) { - $userAddress->node_status = 'Offline'; - $userAddress->extra_status = 'Suspended'; - $userAddress->save(); - } - } - // End For Each - - // Begin For Each - $hasNotOfflineStatus = $hasNotSuspendedStatus = false; - foreach ($addresses as $userAddress) { - if ($userAddress->node_status != 'Offline') { - $hasNotOfflineStatus = true; - break; - } - } - foreach ($addresses as $userAddress) { - if ($userAddress->extra_status != 'Suspended') { - $hasNotSuspendedStatus = true; - break; - } - } - - if (!$hasNotOfflineStatus) { - $user->node_status = 'Offline'; - $user->save(); - } else { - foreach ($addresses as $userAddress) { - if ($userAddress->node_status == 'Online') { - $user->node_status = 'Online'; - $user->save(); - break; - } - } - if ($user->node_status != 'Online') { - $user->node_status = null; - $user->save(); - } - } + if ($user->profile) { + $user->profile->extra_status = null; + $user->profile->save(); + } - if ( - !$hasNotSuspendedStatus && - isset($user->profile) && - $user->profile - ) { - $user->profile->extra_status = 'Suspended'; - $user->profile->save(); - } else if (isset($user->profile) && $user->profile) { - foreach ($addresses as $userAddress) { - if ($userAddress->extra_status == 'On Probation') { - $user->profile->extra_status = 'On Probation'; - $user->save(); - break; - } - } - if ($user->profile->extra_status != 'On Probation') { - $user->profile->extra_status = null; - $user->profile->save(); - } + if ($addresses && count($addresses) > 0) { + foreach ($addresses as $address) { + $address->node_status = null; + $address->extra_status = null; + $address->probation_start = null; + $address->probation_end = null; + $address->save(); } - // End For Each } } } diff --git a/app/Console/Helper.php b/app/Console/Helper.php index d4b998cb..e5a8fa44 100644 --- a/app/Console/Helper.php +++ b/app/Console/Helper.php @@ -45,9 +45,67 @@ public static function getSettings() { if (!isset($settings['redmarks_revoke_calc_size'])) $settings['redmarks_revoke_calc_size'] = 1; if (!isset($settings['responsiveness_warning'])) $settings['responsiveness_warning'] = 1; if (!isset($settings['responsiveness_probation'])) $settings['responsiveness_probation'] = 1; + + $settings['current_era_id'] = self::getCurrentERAId(); + return $settings; } + public static function calculateUptime($baseObject, $public_address_node, $settings = null) { + if (!$settings) $settings = self::getSettings(); + + $uptime_calc_size = (int) ($settings['uptime_calc_size'] ?? 1); + + $temp = DB::select(" + SELECT in_current_era + FROM all_node_data2 + WHERE public_key = '$public_address_node' + ORDER BY era_id DESC + LIMIT $uptime_calc_size + "); + if (!$temp) $temp = []; + + $window = count($temp); + $missed = 0; + foreach ($temp as $c) { + $in = (bool) ($c->in_current_era ?? 0); + if (!$in) { + $missed += 1; + } + } + + $uptime = (float) ($baseObject->uptime ?? 0); + if ($window > 0) { + $uptime = (float) (($uptime * ($window - $missed)) / $window); + } + + return round($uptime, 2); + } + + public static function calculateBadMarks($baseObject, $public_address_node, $settings = null) { + if (!$settings) $settings = self::getSettings(); + + $current_era_id = (int) ($settings['current_era_id'] ?? 0); + $redmarks_revoke_calc_size = (int) ($settings['redmarks_revoke_calc_size'] ?? 1); + + $window = $current_era_id - $redmarks_revoke_calc_size; + if ($window < 0) $window = 0; + + $temp = DB::select(" + SELECT count(era_id) AS bad_marks + FROM all_node_data2 + WHERE public_key = '$public_address_node' + AND era_id > $window + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) + "); + if (!$temp) $temp = []; + + return (int) ($temp[0]->bad_marks ?? 0); + } + public static function getCurrentERAId() { $record = DB::select(" SELECT era_id @@ -80,7 +138,7 @@ public static function publicKeyToAccountHash($public_key) { $public_key = (string)$public_key; $first_byte = substr($public_key, 0, 2); - + if($first_byte === '01') { $algo = unpack('H*', 'ed25519'); } else { @@ -88,7 +146,7 @@ public static function publicKeyToAccountHash($public_key) } $algo = $algo[1] ?? ''; - + $blake2b = new Blake2b(); $account_hash = bin2hex($blake2b->hash(hex2bin($algo.'00'.substr($public_key, 2)))); @@ -105,7 +163,7 @@ public static function getAccountInfoStandard($user) $uid = $user->id ?? 0; $pseudonym = $user->pseudonym ?? null; - + $account_info_urls_uref = getenv('ACCOUNT_INFO_STANDARD_URLS_UREF'); $node_ip = 'http://' . getenv('NODE_IP') . ':7777'; $casper_client = new RpcClient($node_ip); @@ -203,8 +261,7 @@ public static function getAccountInfoStandard($user) } // Get Token Price - public static function getTokenPrice() - { + public static function getTokenPrice() { $url = 'https://pro-api.coinmarketcap.com/v1/tools/price-conversion'; $apiKey = config('services.token_price.api_key'); diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 96bc1d98..348127cb 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -47,6 +47,7 @@ protected function schedule(Schedule $schedule) $schedule->command('token-price:check') ->everyThirtyMinutes() ->runInBackground(); + /* $schedule->command('node-info') ->everyFifteenMinutes() diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 3233ca66..1e643cad 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1662,14 +1662,6 @@ public function canVote() (int) $settings['voting_eras_since_redmark'] : 1; - $redmarks_revoke = (int)($settings['redmarks_revoke'] ?? 1); - $redmarks_revoke_calc_size = (int)($settings['redmarks_revoke_calc_size'] ?? 1); - $window = $current_era_id - $redmarks_revoke_calc_size; - - if ($window < 0) { - $window = 0; - } - $return['setting_voting_eras'] = $voting_eras_to_vote; $return['setting_good_standing_eras'] = $voting_eras_since_redmark; @@ -1718,24 +1710,9 @@ public function canVote() $total_active_eras = $total_active_eras[0] ?? array(); $return['total_active_eras'] = (int)($total_active_eras->tCount ?? 0); - // redmarks - $bad_marks = DB::select(" - SELECT count(era_id) AS bad_marks - FROM all_node_data2 - WHERE public_key = '$p' - AND era_id > $window - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - "); - $bad_marks = $bad_marks[0] ?? []; - $bad_marks = (int)($bad_marks->bad_marks ?? 0); - if ( $return['total_active_eras'] >= $voting_eras_to_vote && - $return['good_standing_eras'] >= $voting_eras_since_redmark && - $bad_marks > $redmarks_revoke + $return['good_standing_eras'] >= $voting_eras_since_redmark ) { $return['can_vote'] = true; } @@ -1792,14 +1769,6 @@ public function vote($id, Request $request) $voting_eras_since_redmark = $voting_eras_since_redmark[0] ?? array(); $voting_eras_since_redmark = (int)($voting_eras_since_redmark->value ?? 0); - $redmarks_revoke = (int)($settings['redmarks_revoke'] ?? 1); - $redmarks_revoke_calc_size = (int)($settings['redmarks_revoke_calc_size'] ?? 1); - $window = $current_era_id - $redmarks_revoke_calc_size; - - if ($window < 0) { - $window = 0; - } - foreach ($addresses as $address) { $p = $address->public_address_node ?? ''; @@ -1836,24 +1805,9 @@ public function vote($id, Request $request) $total_active_eras = $total_active_eras[0] ?? array(); $total_active_eras = (int)($total_active_eras->tCount ?? 0); - // redmarks - $bad_marks = DB::select(" - SELECT count(era_id) AS bad_marks - FROM all_node_data2 - WHERE public_key = '$p' - AND era_id > $window - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - "); - $bad_marks = $bad_marks[0] ?? []; - $bad_marks = (int)($bad_marks->bad_marks ?? 0); - if ( $total_active_eras >= $voting_eras_to_vote && - $good_standing_eras >= $voting_eras_since_redmark && - $bad_marks > $redmarks_revoke + $good_standing_eras >= $voting_eras_since_redmark ) { $stable = true; } diff --git a/database/migrations/2022_11_10_014252_update_user_addresses_6_table.php b/database/migrations/2022_11_10_014252_update_user_addresses_6_table.php new file mode 100644 index 00000000..dd547006 --- /dev/null +++ b/database/migrations/2022_11_10_014252_update_user_addresses_6_table.php @@ -0,0 +1,21 @@ +timestamp('probation_start')->nullable(); + $table->timestamp('probation_end')->nullable(); + }); + } + + public function down() + { + // + } +} From 2430ef388e8b86dc3c42015505bc71fddc1a78c8 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 10 Nov 2022 08:27:33 -0500 Subject: [PATCH 114/162] # Revoke Reason --- app/Console/Commands/CheckNodeStatus.php | 63 ++++++++++--------- app/Console/Helper.php | 4 +- ...022_11_10_033524_update_profile5_table.php | 20 ++++++ 3 files changed, 56 insertions(+), 31 deletions(-) create mode 100644 database/migrations/2022_11_10_033524_update_profile5_table.php diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index dbc9c181..b07a9d57 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -9,7 +9,6 @@ use App\Models\UserAddress; use App\Models\NodeInfo; use App\Models\AllNodeData2; - use Carbon\Carbon; use Illuminate\Support\Facades\DB; @@ -30,6 +29,9 @@ public function handle() { $settings = Helper::getSettings(); $current_era_id = (int) ($settings['current_era_id'] ?? 0); + $redmarks_revoke = (int) ($settings['redmarks_revoke'] ?? 0); + $uptime_probation = (float) ($settings['uptime_probation'] ?? 0); + $now = Carbon::now('UTC'); $users = User::with(['addresses', 'profile']) ->where('role', 'member') @@ -41,13 +43,9 @@ public function handle() { $unit = $settings['uptime_correction_unit']; $value = (float) $settings['uptime_correction_value']; - if ($unit == 'Weeks') { - $uptimeHours = $value * 7 * 24; - } else if ($unit == 'Days') { - $uptimeHours = $value * 24; - } else { - $uptimeHours = $value; - } + if ($unit == 'Weeks') $uptimeHours = $value * 7 * 24; + else if ($unit == 'Days') $uptimeHours = $value * 24; + else $uptimeHours = $value; } foreach ($users as $user) { @@ -66,7 +64,8 @@ public function handle() { // Verified Users $hasOnline = $hasOnProbation = $hasNotSuspended = false; - + $revokeReason = []; + foreach ($addresses as $address) { $public_address_node = strtolower($address->public_address_node); @@ -85,14 +84,26 @@ public function handle() { $address->extra_status = null; $address->save(); + // Check Redmarks + if ($redmarks_revoke > 0) { + $bad_marks = Helper::calculateBadMarks($temp, $public_address_node, $settings); + + if ($bad_marks > $redmarks_revoke) { + $address->extra_status = 'Suspended'; + $address->probation_start = null; + $address->probation_end = null; + $address->save(); + if (!in_array('Too many redmarks', $revokeReason)) { + $revokeReason[] = 'Too many redmarks'; + } + } + } + // Historical Performance - if ( - isset($settings['uptime_probation']) && - (float) $settings['uptime_probation'] > 0 - ) { + if (!$address->extra_status && $uptime_probation > 0) { $uptime = Helper::calculateUptime($temp, $public_address_node, $settings); - if ($uptime < (float) $settings['uptime_probation']) { + if ($uptime < $uptime_probation) { $address->extra_status = 'On Probation'; if (!$address->probation_start || !$address->probation_end) { $address->probation_start = now(); @@ -106,6 +117,9 @@ public function handle() { $address->probation_start = null; $address->probation_end = null; $address->save(); + if (!in_array('Poor uptime', $revokeReason)) { + $revokeReason[] = 'Poor uptime'; + } } else { $hasOnProbation = true; } @@ -116,21 +130,8 @@ public function handle() { } } - // Redmarks - if ( - isset($settings['redmarks_revoke']) && - (int) $settings['redmarks_revoke'] > 0 - ) { - $bad_marks = Helper::calculateBadMarks($temp, $public_address_node, $settings); - - if ($bad_marks > (int) $settings['redmarks_revoke']) { - $address->extra_status = 'Suspended'; - $address->probation_start = null; - $address->probation_end = null; - $address->save(); - } else { - $hasNotSuspended = true; - } + if ($address->extra_status != 'Suspended') { + $hasNotSuspended = true; } } else { // All Node Data2 Record Doesn't Exist! @@ -161,6 +162,9 @@ public function handle() { if ($hasOnline && !$hasNotSuspended) { $user->profile->extra_status = 'Suspended'; + if (count($revokeReason) > 0) { + $user->profile->revoke_reason = implode(', ', $revokeReason); + } $user->profile->save(); } } else { @@ -171,6 +175,7 @@ public function handle() { if ($user->profile) { $user->profile->extra_status = null; + $user->profile->revoke_reason = null; $user->profile->save(); } diff --git a/app/Console/Helper.php b/app/Console/Helper.php index e5a8fa44..be261109 100644 --- a/app/Console/Helper.php +++ b/app/Console/Helper.php @@ -53,7 +53,7 @@ public static function getSettings() { public static function calculateUptime($baseObject, $public_address_node, $settings = null) { if (!$settings) $settings = self::getSettings(); - + $uptime_calc_size = (int) ($settings['uptime_calc_size'] ?? 1); $temp = DB::select(" @@ -87,7 +87,7 @@ public static function calculateBadMarks($baseObject, $public_address_node, $set $current_era_id = (int) ($settings['current_era_id'] ?? 0); $redmarks_revoke_calc_size = (int) ($settings['redmarks_revoke_calc_size'] ?? 1); - + $window = $current_era_id - $redmarks_revoke_calc_size; if ($window < 0) $window = 0; diff --git a/database/migrations/2022_11_10_033524_update_profile5_table.php b/database/migrations/2022_11_10_033524_update_profile5_table.php new file mode 100644 index 00000000..b320e69a --- /dev/null +++ b/database/migrations/2022_11_10_033524_update_profile5_table.php @@ -0,0 +1,20 @@ +string('revoke_reason', 255)->nullable(); + }); + } + + public function down() + { + // + } +} From 8d531059919b86f378a49f45c177b5c677514b9a Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 10 Nov 2022 09:57:19 -0500 Subject: [PATCH 115/162] # Node Status Update --- app/Console/Commands/CheckNodeStatus.php | 176 +++++++++--------- .../Controllers/Api/V1/UserController.php | 68 +++++++ routes/api.php | 1 + 3 files changed, 160 insertions(+), 85 deletions(-) diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index b07a9d57..2cbe32c2 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -27,8 +27,8 @@ public function __construct() { public function handle() { $settings = Helper::getSettings(); - $current_era_id = (int) ($settings['current_era_id'] ?? 0); + $current_era_id = (int) ($settings['current_era_id'] ?? 0); $redmarks_revoke = (int) ($settings['redmarks_revoke'] ?? 0); $uptime_probation = (float) ($settings['uptime_probation'] ?? 0); @@ -63,109 +63,115 @@ public function handle() { ) { // Verified Users - $hasOnline = $hasOnProbation = $hasNotSuspended = false; - $revokeReason = []; - - foreach ($addresses as $address) { - $public_address_node = strtolower($address->public_address_node); + if ($user->profile->extra_status == 'Suspended') { + // Suspended + } else { + // Not Suspended - $temp = AllNodeData2::select(['id', 'uptime']) - ->where('public_key', $public_address_node) - ->where('era_id', $current_era_id) - ->where('bid_inactive', 0) - ->where('in_auction', 1) - ->where('in_current_era', 1) - ->first(); - if ($temp) { - // All Node Data2 Record Exists! + $hasOnline = $hasOnProbation = $hasSuspended = false; + $revokeReason = []; - $hasOnline = true; - $address->node_status = 'Online'; - $address->extra_status = null; - $address->save(); - - // Check Redmarks - if ($redmarks_revoke > 0) { - $bad_marks = Helper::calculateBadMarks($temp, $public_address_node, $settings); - - if ($bad_marks > $redmarks_revoke) { - $address->extra_status = 'Suspended'; - $address->probation_start = null; - $address->probation_end = null; - $address->save(); - if (!in_array('Too many redmarks', $revokeReason)) { - $revokeReason[] = 'Too many redmarks'; + foreach ($addresses as $address) { + $public_address_node = strtolower($address->public_address_node); + + $temp = AllNodeData2::select(['id', 'uptime']) + ->where('public_key', $public_address_node) + ->where('era_id', $current_era_id) + ->where('bid_inactive', 0) + ->where('in_auction', 1) + ->where('in_current_era', 1) + ->first(); + if ($temp) { + // All Node Data2 Record Exists! + + $hasOnline = true; + $address->node_status = 'Online'; + $address->extra_status = null; + $address->save(); + + // Check Redmarks + if ($redmarks_revoke > 0) { + $bad_marks = Helper::calculateBadMarks($temp, $public_address_node, $settings); + + if ($bad_marks > $redmarks_revoke) { + $address->extra_status = 'Suspended'; + $address->probation_start = null; + $address->probation_end = null; + $address->save(); + if (!in_array('Too many redmarks', $revokeReason)) { + $revokeReason[] = 'Too many redmarks'; + } } } - } - // Historical Performance - if (!$address->extra_status && $uptime_probation > 0) { - $uptime = Helper::calculateUptime($temp, $public_address_node, $settings); + // Check Historical Performance + if (!$address->extra_status && $uptime_probation > 0) { + $uptime = Helper::calculateUptime($temp, $public_address_node, $settings); - if ($uptime < $uptime_probation) { - $address->extra_status = 'On Probation'; - if (!$address->probation_start || !$address->probation_end) { - $address->probation_start = now(); - $address->probation_end = Carbon::now('UTC')->addHours($uptimeHours); - } - $address->save(); + if ($uptime < $uptime_probation) { + $address->extra_status = 'On Probation'; + if (!$address->probation_start || !$address->probation_end) { + $address->probation_start = now(); + $address->probation_end = Carbon::now('UTC')->addHours($uptimeHours); + } + $address->save(); - $now = Carbon::now('UTC'); - if ($address->probation_end <= $now) { - $address->extra_status = 'Suspended'; + $now = Carbon::now('UTC'); + if ($address->probation_end <= $now) { + $address->extra_status = 'Suspended'; + $address->probation_start = null; + $address->probation_end = null; + $address->save(); + if (!in_array('Poor uptime', $revokeReason)) { + $revokeReason[] = 'Poor uptime'; + } + } else { + $hasOnProbation = true; + } + } else { $address->probation_start = null; $address->probation_end = null; $address->save(); - if (!in_array('Poor uptime', $revokeReason)) { - $revokeReason[] = 'Poor uptime'; - } - } else { - $hasOnProbation = true; } - } else { - $address->probation_start = null; - $address->probation_end = null; - $address->save(); } - } - if ($address->extra_status != 'Suspended') { - $hasNotSuspended = true; + if ($address->extra_status == 'Suspended') { + $hasSuspended = true; + } + } else { + // All Node Data2 Record Doesn't Exist! + + $address->node_status = 'Offline'; + $address->extra_status = null; + $address->probation_start = null; + $address->probation_end = null; + $address->save(); } - } else { - // All Node Data2 Record Doesn't Exist! - - $address->node_status = 'Offline'; - $address->extra_status = null; - $address->probation_start = null; - $address->probation_end = null; - $address->save(); } - } - if ($hasOnline) { - $user->node_status = 'Online'; - $user->save(); - } else { - $user->node_status = 'Offline'; - $user->save(); - } + if ($hasOnline) { + $user->node_status = 'Online'; + $user->save(); + } else { + $user->node_status = 'Offline'; + $user->save(); + } - if ($hasOnProbation) { - $user->profile->extra_status = 'On Probation'; - $user->profile->save(); - } else { - $user->profile->extra_status = null; - $user->profile->save(); - } + if ($hasOnProbation) { + $user->profile->extra_status = 'On Probation'; + $user->profile->save(); + } else { + $user->profile->extra_status = null; + $user->profile->save(); + } - if ($hasOnline && !$hasNotSuspended) { - $user->profile->extra_status = 'Suspended'; - if (count($revokeReason) > 0) { - $user->profile->revoke_reason = implode(', ', $revokeReason); + if ($hasSuspended) { + $user->profile->extra_status = 'Suspended'; + if (count($revokeReason) > 0) { + $user->profile->revoke_reason = implode(', ', $revokeReason); + } + $user->profile->save(); } - $user->profile->save(); } } else { // Non-Verified Users diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 1e643cad..b9e3091d 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1636,6 +1636,74 @@ public function getVoteDetail($id) return $this->successResponse($ballot); } + public function canRequestReactivation() + { + $user = auth()->user()->load(['addresses', 'profile']); + $user_id = $user->id; + $addresses = $user->addresses ?? []; + + $settings = Helper::getSettings(); + + $current_era_id = (int) ($settings['current_era_id'] ?? 0); + $redmarks_revoke = (int) ($settings['redmarks_revoke'] ?? 0); + $uptime_probation = (float) ($settings['uptime_probation'] ?? 0); + + $return = [ + 'can_request_reactivation' => false + ]; + + if ( + $addresses && + count($addresses) > 0 && + $user->node_verified_at && + $user->letter_verified_at && + $user->signature_request_id && + isset($user->profile) && + $user->profile && + $user->profile->status == 'approved' && + $user->profile->extra_status == 'Suspended' + ) { + $flag = true; + foreach ($addresses as $address) { + $public_address_node = strtolower($address->public_address_node); + + $temp = AllNodeData2::select(['id', 'uptime']) + ->where('public_key', $public_address_node) + ->where('era_id', $current_era_id) + ->where('bid_inactive', 0) + ->where('in_auction', 1) + ->where('in_current_era', 1) + ->first(); + + if ($temp && $address->extra_status == 'Suspended') { + // Check Redmarks + if ($redmarks_revoke > 0) { + $bad_marks = Helper::calculateBadMarks($temp, $public_address_node, $settings); + if ($bad_marks > $redmarks_revoke) { + $flag = false; + break; + } + } + + // Check Historical Performance + if ($uptime_probation > 0) { + $uptime = Helper::calculateUptime($temp, $public_address_node, $settings); + if ($uptime < $uptime_probation) { + $flag = false; + break; + } + } + } + } + + $return = [ + 'can_request_reactivation' => $flag + ]; + } + + return $this->successResponse($return); + } + public function canVote() { $user = auth()->user(); diff --git a/routes/api.php b/routes/api.php index 2e0c5d54..3107dad3 100644 --- a/routes/api.php +++ b/routes/api.php @@ -69,6 +69,7 @@ // New endpoint for User voting eligibility check Route::get('/users/can-vote', [UserController::class, 'canVote']); + Route::post('/users/can-request-reactivation', [UserController::class, 'canRequestReactivation']); Route::post('/users/verify-email', [AuthController::class, 'verifyEmail']); Route::post('/users/resend-verify-email', [AuthController::class, 'resendVerifyEmail']); From 7e6b1aff020266bbb30889d1c59ecd57b9e198eb Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 10 Nov 2022 12:41:35 -0500 Subject: [PATCH 116/162] # Reactivation Updates --- app/Console/Commands/CheckNodeStatus.php | 4 +- app/Console/Helper.php | 1 + .../Controllers/Api/V1/UserController.php | 77 ++++++++++++++----- ...022_11_10_165306_update_profile6_table.php | 22 ++++++ routes/api.php | 3 +- 5 files changed, 84 insertions(+), 23 deletions(-) create mode 100644 database/migrations/2022_11_10_165306_update_profile6_table.php diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index 2cbe32c2..91b37fee 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -92,7 +92,7 @@ public function handle() { // Check Redmarks if ($redmarks_revoke > 0) { $bad_marks = Helper::calculateBadMarks($temp, $public_address_node, $settings); - + if ($bad_marks > $redmarks_revoke) { $address->extra_status = 'Suspended'; $address->probation_start = null; @@ -165,7 +165,7 @@ public function handle() { $user->profile->save(); } - if ($hasSuspended) { + if ($hasOnline && $hasSuspended) { $user->profile->extra_status = 'Suspended'; if (count($revokeReason) > 0) { $user->profile->revoke_reason = implode(', ', $revokeReason); diff --git a/app/Console/Helper.php b/app/Console/Helper.php index be261109..d4a7fed1 100644 --- a/app/Console/Helper.php +++ b/app/Console/Helper.php @@ -10,6 +10,7 @@ use App\Models\Shuftipro; use App\Models\User; use App\Models\Setting; +use App\Models\AllNodeData2; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Http; diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index b9e3091d..2174774d 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1636,6 +1636,34 @@ public function getVoteDetail($id) return $this->successResponse($ballot); } + public function requestReactivation(Request $request) + { + $user = auth()->user()->load(['addresses', 'profile']); + $addresses = $user->addresses ?? []; + + $reactivation_reason = $request->reactivationReason ?? ''; + + if ( + $addresses && + count($addresses) > 0 && + $user->node_verified_at && + $user->letter_verified_at && + $user->signature_request_id && + isset($user->profile) && + $user->profile && + $user->profile->status == 'approved' && + $user->profile->extra_status == 'Suspended' && + !$user->profile->reactivation_requested + ) { + $user->profile->reactivation_reason = $reactivation_reason; + $user->profile->reactivation_requested = true; + $user->profile->reactivation_requested_at = now(); + $user->profile->save(); + } + + return $this->metaSuccess(); + } + public function canRequestReactivation() { $user = auth()->user()->load(['addresses', 'profile']); @@ -1649,7 +1677,9 @@ public function canRequestReactivation() $uptime_probation = (float) ($settings['uptime_probation'] ?? 0); $return = [ - 'can_request_reactivation' => false + 'canRequest' => false, + 'avg_uptime' => 0, + 'avg_redmarks' => 0 ]; if ( @@ -1664,41 +1694,48 @@ public function canRequestReactivation() $user->profile->extra_status == 'Suspended' ) { $flag = true; + $count = 0; foreach ($addresses as $address) { $public_address_node = strtolower($address->public_address_node); - $temp = AllNodeData2::select(['id', 'uptime']) + if ($address->extra_status == 'Suspended') { + $count++; + + $temp = AllNodeData2::select(['id', 'uptime']) ->where('public_key', $public_address_node) ->where('era_id', $current_era_id) ->where('bid_inactive', 0) ->where('in_auction', 1) ->where('in_current_era', 1) ->first(); - - if ($temp && $address->extra_status == 'Suspended') { - // Check Redmarks - if ($redmarks_revoke > 0) { - $bad_marks = Helper::calculateBadMarks($temp, $public_address_node, $settings); - if ($bad_marks > $redmarks_revoke) { - $flag = false; - break; + if ($temp) { + // Check Redmarks + if ($redmarks_revoke > 0) { + $bad_marks = Helper::calculateBadMarks($temp, $public_address_node, $settings); + if ($bad_marks > $redmarks_revoke) { + $flag = false; + } + $return['avg_redmarks'] += $bad_marks; } - } - // Check Historical Performance - if ($uptime_probation > 0) { - $uptime = Helper::calculateUptime($temp, $public_address_node, $settings); - if ($uptime < $uptime_probation) { - $flag = false; - break; + // Check Historical Performance + if ($uptime_probation > 0) { + $uptime = Helper::calculateUptime($temp, $public_address_node, $settings); + if ($uptime < $uptime_probation) { + $flag = false; + } + $return['avg_uptime'] += $uptime; } } } } - $return = [ - 'can_request_reactivation' => $flag - ]; + $return['canRequest'] = $flag; + if ($count <= 0) $count = 1; + + $return['avg_redmarks'] = (int) ($return['avg_redmarks'] / $count); + $return['avg_uptime'] = (float) ($return['avg_uptime'] / $count); + $return['avg_uptime'] = round($return['avg_uptime'], 2); } return $this->successResponse($return); diff --git a/database/migrations/2022_11_10_165306_update_profile6_table.php b/database/migrations/2022_11_10_165306_update_profile6_table.php new file mode 100644 index 00000000..c835b2b1 --- /dev/null +++ b/database/migrations/2022_11_10_165306_update_profile6_table.php @@ -0,0 +1,22 @@ +text('reactivation_reason')->nullable(); + $table->boolean('reactivation_requested')->nullable(); + $table->timestamp('reactivation_requested_at')->nullable(); + }); + } + + public function down() + { + // + } +} diff --git a/routes/api.php b/routes/api.php index 3107dad3..236a9e06 100644 --- a/routes/api.php +++ b/routes/api.php @@ -70,7 +70,8 @@ // New endpoint for User voting eligibility check Route::get('/users/can-vote', [UserController::class, 'canVote']); Route::post('/users/can-request-reactivation', [UserController::class, 'canRequestReactivation']); - + Route::post('/users/request-reactivation', [UserController::class, 'requestReactivation']); + Route::post('/users/verify-email', [AuthController::class, 'verifyEmail']); Route::post('/users/resend-verify-email', [AuthController::class, 'resendVerifyEmail']); Route::post('/users/change-email', [UserController::class, 'changeEmail']); From 48fab4c75a001ad26ee0a5d44844ee98c87cee9a Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Fri, 11 Nov 2022 03:39:12 -0500 Subject: [PATCH 117/162] # Updates --- app/Console/Commands/CheckNodeStatus.php | 6 +++++- .../Controllers/Api/V1/AdminController.php | 10 ++++++++++ ...022_11_11_083251_update_profile7_table.php | 20 +++++++++++++++++++ routes/api.php | 3 +++ 4 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 database/migrations/2022_11_11_083251_update_profile7_table.php diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index 91b37fee..9a937de0 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -62,9 +62,12 @@ public function handle() { $user->profile->status == 'approved' ) { // Verified Users - if ($user->profile->extra_status == 'Suspended') { // Suspended + if (!$user->profile->revoke_at) { + $user->profile->revoke_at = now(); + $user->profile->save(); + } } else { // Not Suspended @@ -167,6 +170,7 @@ public function handle() { if ($hasOnline && $hasSuspended) { $user->profile->extra_status = 'Suspended'; + $user->profile->revoke_at = now(); if (count($revokeReason) > 0) { $user->profile->revoke_reason = implode(', ', $revokeReason); } diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index aa9f0b9a..6b3a84e9 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -457,6 +457,16 @@ public function getNodesPage() { return $this->successResponse($return); } + public function getActiveReinstatements() + { + $items = Profile::where('extra_status', 'Suspended') + ->where('reactivation_requested', true) + ->whereNotNull('reactivation_requested_at') + ->orderBy('reactivation_requested_at', 'desc') + ->get(); + return $this->successResponse($items); + } + public function getUsers(Request $request) { /* diff --git a/database/migrations/2022_11_11_083251_update_profile7_table.php b/database/migrations/2022_11_11_083251_update_profile7_table.php new file mode 100644 index 00000000..f6534e52 --- /dev/null +++ b/database/migrations/2022_11_11_083251_update_profile7_table.php @@ -0,0 +1,20 @@ +timestamp('revoke_at')->nullable(); + }); + } + + public function down() + { + // + } +} diff --git a/routes/api.php b/routes/api.php index 236a9e06..2b9e82be 100644 --- a/routes/api.php +++ b/routes/api.php @@ -133,6 +133,9 @@ Route::get('/users/{id}', [AdminController::class, 'getUserDetail'])->where('id', '[0-9]+'); Route::get('/dashboard', [AdminController::class, 'infoDashboard']); + Route::get('/active-reinstatements', [AdminController::class, 'getActiveReinstatements']); + + // intakes Route::middleware([])->group(function () { Route::get('/users/intakes', [AdminController::class, 'getIntakes']); From dcdcfba7729a82a3b6e9d1030b36b47c9ccc1aee Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Fri, 11 Nov 2022 09:07:42 -0500 Subject: [PATCH 118/162] # Approve/Reject Reinstatement --- .../Controllers/Api/V1/AdminController.php | 112 +++++++++++++++++- app/Models/Profile.php | 5 + app/Models/ReinstatementHistory.php | 17 +++ ...749_create_reinstatement_history_table.php | 28 +++++ routes/api.php | 3 + 5 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 app/Models/ReinstatementHistory.php create mode 100644 database/migrations/2022_11_11_090749_create_reinstatement_history_table.php diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 6b3a84e9..7d9bc6e9 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -43,6 +43,7 @@ use App\Models\VoteResult; use App\Models\ContactRecipient; use App\Models\AllNodeData2; +use App\Models\ReinstatementHistory; use App\Services\NodeHelper; @@ -459,7 +460,9 @@ public function getNodesPage() { public function getActiveReinstatements() { - $items = Profile::where('extra_status', 'Suspended') + $items = Profile::with('user') + ->has('user') + ->where('extra_status', 'Suspended') ->where('reactivation_requested', true) ->whereNotNull('reactivation_requested_at') ->orderBy('reactivation_requested_at', 'desc') @@ -467,6 +470,113 @@ public function getActiveReinstatements() return $this->successResponse($items); } + public function getHistoryReinstatements() + { + $items = ReinstatementHistory::with('user') + ->has('user') + ->whereNotNull('decision_at') + ->orderBy('decision_at', 'desc') + ->get(); + return $this->successResponse($items); + } + + public function approveReinstatement(Request $request) + { + $profileId = (int) ($request->profileId ?? 0); + $profile = Profile::find($profileId); + + if (!$profile) { + return $this->errorResponse(__('api.error.not_found'), Response::HTTP_NOT_FOUND); + } + + if ($profile->extra_status != 'Suspended' || !$profile->reactivation_requested) { + return $this->errorResponse(__('api.error.not_found'), Response::HTTP_NOT_FOUND); + } + + $userId = (int) $profile->user_id; + $revoke_at = $profile->revoke_at; + $revoke_reason = $profile->revoke_reason; + $reactivation_reason = $profile->reactivation_reason; + $reactivation_requested_at = $profile->reactivation_requested_at; + + if (!$revoke_at || !$reactivation_requested_at) { + return $this->errorResponse(__('api.error.not_found'), Response::HTTP_NOT_FOUND); + } + + $historyRecord = ReinstatementHistory::where('revoke_at', $revoke_at) + ->where('reactivation_requested_at', $reactivation_requested_at) + ->where('user_id', $userId) + ->first(); + if (!$historyRecord) { + $historyRecord = new ReinstatementHistory; + $historyRecord->user_id = $userId; + $historyRecord->revoke_at = $revoke_at; + $historyRecord->revoke_reason = $revoke_reason; + $historyRecord->reactivation_reason = $reactivation_reason; + $historyRecord->reactivation_requested_at = $reactivation_requested_at; + $historyRecord->decision = true; + $historyRecord->decision_at = now(); + $historyRecord->save(); + } + + $profile->extra_status = null; + $profile->revoke_at = null; + $profile->revoke_reason = null; + $profile->reactivation_reason = null; + $profile->reactivation_requested = null; + $profile->reactivation_requested_at = null; + $profile->save(); + + $this->metaSuccess(); + } + + public function rejectReinstatement(Request $request) + { + $profileId = (int) ($request->profileId ?? 0); + $profile = Profile::find($profileId); + + if (!$profile) { + return $this->errorResponse(__('api.error.not_found'), Response::HTTP_NOT_FOUND); + } + + if ($profile->extra_status != 'Suspended' || !$profile->reactivation_requested) { + return $this->errorResponse(__('api.error.not_found'), Response::HTTP_NOT_FOUND); + } + + $userId = (int) $profile->user_id; + $revoke_at = $profile->revoke_at; + $revoke_reason = $profile->revoke_reason; + $reactivation_reason = $profile->reactivation_reason; + $reactivation_requested_at = $profile->reactivation_requested_at; + + if (!$revoke_at || !$reactivation_requested_at) { + return $this->errorResponse(__('api.error.not_found'), Response::HTTP_NOT_FOUND); + } + + $historyRecord = ReinstatementHistory::where('revoke_at', $revoke_at) + ->where('reactivation_requested_at', $reactivation_requested_at) + ->where('user_id', $userId) + ->first(); + if (!$historyRecord) { + $historyRecord = new ReinstatementHistory; + $historyRecord->user_id = $userId; + $historyRecord->revoke_at = $revoke_at; + $historyRecord->revoke_reason = $revoke_reason; + $historyRecord->reactivation_reason = $reactivation_reason; + $historyRecord->reactivation_requested_at = $reactivation_requested_at; + $historyRecord->decision = false; + $historyRecord->decision_at = now(); + $historyRecord->save(); + } + + $profile->reactivation_reason = null; + $profile->reactivation_requested = null; + $profile->reactivation_requested_at = null; + $profile->save(); + + $this->metaSuccess(); + } + public function getUsers(Request $request) { /* diff --git a/app/Models/Profile.php b/app/Models/Profile.php index 280065fb..4b1cd764 100644 --- a/app/Models/Profile.php +++ b/app/Models/Profile.php @@ -18,4 +18,9 @@ public function getDocumentVerifiedAtAttribute($value) } return null; } + + public function user() + { + return $this->hasOne('App\Models\User', 'id', 'user_id'); + } } diff --git a/app/Models/ReinstatementHistory.php b/app/Models/ReinstatementHistory.php new file mode 100644 index 00000000..434ef07c --- /dev/null +++ b/app/Models/ReinstatementHistory.php @@ -0,0 +1,17 @@ +hasOne('App\Models\User', 'id', 'user_id'); + } +} diff --git a/database/migrations/2022_11_11_090749_create_reinstatement_history_table.php b/database/migrations/2022_11_11_090749_create_reinstatement_history_table.php new file mode 100644 index 00000000..ff7b0bab --- /dev/null +++ b/database/migrations/2022_11_11_090749_create_reinstatement_history_table.php @@ -0,0 +1,28 @@ +id(); + $table->integer('user_id'); + $table->timestamp('revoke_at')->nullable(); + $table->string('revoke_reason', 255)->nullable(); + $table->text('reactivation_reason')->nullable(); + $table->timestamp('reactivation_requested_at')->nullable(); + $table->boolean('decision')->nullable(); + $table->timestamp('decision_at')->nullable(); + $table->timestamps(); + }); + } + + public function down() + { + Schema::dropIfExists('reinstatement_history'); + } +} diff --git a/routes/api.php b/routes/api.php index 2b9e82be..635527e2 100644 --- a/routes/api.php +++ b/routes/api.php @@ -134,7 +134,10 @@ Route::get('/dashboard', [AdminController::class, 'infoDashboard']); Route::get('/active-reinstatements', [AdminController::class, 'getActiveReinstatements']); + Route::get('/history-reinstatements', [AdminController::class, 'getHistoryReinstatements']); + Route::post('/approve-reinstatement', [AdminController::class, 'approveReinstatement']); + Route::post('/reject-reinstatement', [AdminController::class, 'rejectReinstatement']); // intakes Route::middleware([])->group(function () { From 2554ccd7e4a5df292beecd7bca6306c5910d98e4 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Fri, 11 Nov 2022 10:42:16 -0500 Subject: [PATCH 119/162] # Admin Reactivate/Revoke --- .../Controllers/Api/V1/AdminController.php | 63 ++++++++++++++++++- routes/api.php | 2 + 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 7d9bc6e9..8b38c31a 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -480,6 +480,58 @@ public function getHistoryReinstatements() return $this->successResponse($items); } + public function revokeUser($id) + { + $user = User::with('profile') + ->has('profile') + ->where('id', $id) + ->where('role', 'member') + ->first(); + + if ($user && $user->profile->status == 'approved' && $user->profile->extra_status != 'Suspended') { + $user->profile->extra_status = 'Suspended'; + $user->profile->revoke_reason = 'Admin action'; + $user->profile->revoke_at = now(); + $user->profile->reactivation_reason = null; + $user->profile->reactivation_requested = null; + $user->profile->reactivation_requested_at = null; + $user->profile->save(); + + return $this->metaSuccess(); + } + + return $this->errorResponse('Fail Revoke User', Response::HTTP_BAD_REQUEST); + } + + public function reactivateUser($id) + { + $user = User::with('profile') + ->has('profile') + ->where('id', $id) + ->where('role', 'member') + ->first(); + + if ( + $user && + $user->profile->status == 'approved' && + $user->profile->extra_status == 'Suspended' && + $user->profile->revoke_reason && + $user->profile->revoke_at + ) { + $user->profile->extra_status = null; + $user->profile->revoke_reason = null; + $user->profile->revoke_at = null; + $user->profile->reactivation_reason = null; + $user->profile->reactivation_requested = null; + $user->profile->reactivation_requested_at = null; + $user->profile->save(); + + return $this->metaSuccess(); + } + + return $this->errorResponse('Fail Reactivate User', Response::HTTP_BAD_REQUEST); + } + public function approveReinstatement(Request $request) { $profileId = (int) ($request->profileId ?? 0); @@ -613,7 +665,7 @@ public function getUsers(Request $request) a.signature_request_id, a.node_status, a.node_verified_at, a.member_status, a.kyc_verified_at, b.dob, b.country_citizenship, b.country_residence, - b.status AS profile_status, b.extra_status, + b.status AS profile_status, b.extra_status, b.revoke_reason, b.type, b.casper_association_kyc_hash, b.blockchain_name, b.blockchain_desc FROM users AS a @@ -667,6 +719,9 @@ public function getUsers(Request $request) $status = 'Verified'; if ($user->extra_status) { $status = $user->extra_status; + if ($user->extra_status == 'Suspended') { + $status = 'Revoked'; + } } } @@ -707,6 +762,12 @@ public function getUserDetail($id) $status = 'Verified'; if ($user->profile->extra_status) { $status = $user->profile->extra_status; + if ($user->profile->extra_status == 'Suspended') { + $status = 'Revoked'; + if ($user->profile->revoke_reason) { + $status = 'Revoked for ' . $user->profile->revoke_reason; + } + } } } diff --git a/routes/api.php b/routes/api.php index 635527e2..d7c12fbd 100644 --- a/routes/api.php +++ b/routes/api.php @@ -145,6 +145,8 @@ Route::post('/users/intakes/{id}/approve', [AdminController::class, 'approveIntakeUser'])->where('id', '[0-9]+'); Route::post('/users/intakes/{id}/reset', [AdminController::class, 'resetIntakeUser'])->where('id', '[0-9]+'); Route::post('/users/{id}/ban', [AdminController::class, 'banUser'])->where('id', '[0-9]+'); + Route::post('/users/{id}/revoke', [AdminController::class, 'revokeUser'])->where('id', '[0-9]+'); + Route::post('/users/{id}/reactivate', [AdminController::class, 'reactivateUser'])->where('id', '[0-9]+'); Route::post('/users/{id}/remove', [AdminController::class, 'removeUser'])->where('id', '[0-9]+'); }); From df7557ca35244a6e64da9bb0c00cdfdf47f73b9b Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Mon, 14 Nov 2022 20:06:27 -0500 Subject: [PATCH 120/162] # Updates --- app/Console/Commands/CheckNodeStatus.php | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index 9a937de0..57971602 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -32,7 +32,6 @@ public function handle() { $redmarks_revoke = (int) ($settings['redmarks_revoke'] ?? 0); $uptime_probation = (float) ($settings['uptime_probation'] ?? 0); - $now = Carbon::now('UTC'); $users = User::with(['addresses', 'profile']) ->where('role', 'member') ->where('banned', 0) @@ -65,7 +64,7 @@ public function handle() { if ($user->profile->extra_status == 'Suspended') { // Suspended if (!$user->profile->revoke_at) { - $user->profile->revoke_at = now(); + $user->profile->revoke_at = Carbon::now('UTC'); $user->profile->save(); } } else { @@ -114,13 +113,12 @@ public function handle() { if ($uptime < $uptime_probation) { $address->extra_status = 'On Probation'; if (!$address->probation_start || !$address->probation_end) { - $address->probation_start = now(); + $address->probation_start = Carbon::now('UTC'); $address->probation_end = Carbon::now('UTC')->addHours($uptimeHours); } $address->save(); - $now = Carbon::now('UTC'); - if ($address->probation_end <= $now) { + if ($address->probation_end <= Carbon::now('UTC')) { $address->extra_status = 'Suspended'; $address->probation_start = null; $address->probation_end = null; @@ -170,7 +168,7 @@ public function handle() { if ($hasOnline && $hasSuspended) { $user->profile->extra_status = 'Suspended'; - $user->profile->revoke_at = now(); + $user->profile->revoke_at = Carbon::now('UTC'); if (count($revokeReason) > 0) { $user->profile->revoke_reason = implode(', ', $revokeReason); } From 5997f1b35f7e21ad2a9d03624f39fce4fcfb4291 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 16 Nov 2022 09:35:52 -0500 Subject: [PATCH 121/162] # Delete Discussion --- .../Api/V1/DiscussionController.php | 27 +++++++++++++++++++ routes/api.php | 1 + 2 files changed, 28 insertions(+) diff --git a/app/Http/Controllers/Api/V1/DiscussionController.php b/app/Http/Controllers/Api/V1/DiscussionController.php index 99223607..8a16130f 100644 --- a/app/Http/Controllers/Api/V1/DiscussionController.php +++ b/app/Http/Controllers/Api/V1/DiscussionController.php @@ -21,6 +21,7 @@ use App\Models\DiscussionComment; use App\Models\DiscussionPin; use App\Models\DiscussionRemoveNew; +use App\Models\DiscussionVote; use Carbon\Carbon; @@ -409,6 +410,32 @@ public function getDraftDiscussions(Request $request) return $this->successResponse($data); } + public function deleteDiscussion($id) + { + $user = auth()->user()->load(['pagePermissions']); + if (Helper::isAccessBlocked($user, 'discussions')) + return $this->errorResponse('Your access is blocked', Response::HTTP_BAD_REQUEST); + + $discussion = Discussion::where('id', $id) + ->where(function ($query) use ($user) { + if ($user->role != 'admin') { + $query->where('user_id', $user->id); + } + }) + ->first(); + if (!$discussion) { + return $this->errorResponse('Can not delete discussion', Response::HTTP_BAD_REQUEST); + } + + DiscussionComment::where('discussion_id', $discussion->id)->delete(); + DiscussionPin::where('discussion_id', $discussion->id)->delete(); + DiscussionRemoveNew::where('discussion_id', $discussion->id)->delete(); + DiscussionVote::where('discussion_id', $discussion->id)->delete(); + $discussion->delete(); + + return $this->metaSuccess(); + } + public function deleteDraftDiscussions($id) { $user = auth()->user()->load(['pagePermissions']); diff --git a/routes/api.php b/routes/api.php index d7c12fbd..d526b8b8 100644 --- a/routes/api.php +++ b/routes/api.php @@ -227,6 +227,7 @@ Route::get('/detail/{id}', [DiscussionController::class, 'getDiscussion']); Route::post('/new', [DiscussionController::class, 'postDiscussion']); Route::put('/{id}', [DiscussionController::class, 'updateDiscussion']); + Route::delete('/{id}', [DiscussionController::class, 'deleteDiscussion']); Route::delete('/{id}/new', [DiscussionController::class, 'removeNewMark']); Route::post('/{id}/comment', [DiscussionController::class, 'createComment']); Route::put('/{id}/comment', [DiscussionController::class, 'updateComment']); From a7da26025d86b6e39bdc774c98a780a5ef1cefaf Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 16 Nov 2022 11:46:56 -0500 Subject: [PATCH 122/162] # Comment API --- app/Http/Controllers/Api/V1/DiscussionController.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/Http/Controllers/Api/V1/DiscussionController.php b/app/Http/Controllers/Api/V1/DiscussionController.php index 8a16130f..634960d8 100644 --- a/app/Http/Controllers/Api/V1/DiscussionController.php +++ b/app/Http/Controllers/Api/V1/DiscussionController.php @@ -288,7 +288,9 @@ public function updateComment(Request $request, $id) if ($validator->fails()) { return $this->validateResponse($validator->errors()); } - $comment = DiscussionComment::where('discussion_id', $request->comment_id)->where('user_id', $user->id)->first(); + $comment = DiscussionComment::where('id', $request->comment_id) + ->where('discussion_id', $id) + ->where('user_id', $user->id)->first(); if ($comment) { $comment->description = $request->description; $comment->save(); From c7800b7cb3aa9d4916fb9d2f403185279a0f6d6d Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Wed, 16 Nov 2022 20:32:02 -0500 Subject: [PATCH 123/162] # Comment Updates --- .../Api/V1/DiscussionController.php | 30 +++++++++++++++---- ...004030_update_discussion_comment_table.php | 16 ++++++++++ routes/api.php | 1 + 3 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 database/migrations/2022_11_17_004030_update_discussion_comment_table.php diff --git a/app/Http/Controllers/Api/V1/DiscussionController.php b/app/Http/Controllers/Api/V1/DiscussionController.php index 634960d8..9ece9b02 100644 --- a/app/Http/Controllers/Api/V1/DiscussionController.php +++ b/app/Http/Controllers/Api/V1/DiscussionController.php @@ -83,7 +83,6 @@ public function getDiscussions(Request $request) if (Helper::isAccessBlocked($user, 'discussions')) return $this->successResponse(['data' => []]); - $data = array(); $limit = $request->limit ?? 50; $data = Discussion::with(['user', 'user.profile'])->where('discussions.is_draft', 0) ->leftJoin('discussion_pins', function ($query) use ($user) { @@ -248,7 +247,7 @@ public function createComment(Request $request, $id) if (Helper::isAccessBlocked($user, 'discussions')) return $this->errorResponse('Your access is blocked', Response::HTTP_BAD_REQUEST); - $data = array(); + $data = []; $validator = Validator::make($request->all(), [ 'description' => 'required' ]); @@ -280,7 +279,6 @@ public function updateComment(Request $request, $id) if (Helper::isAccessBlocked($user, 'discussions')) return $this->errorResponse('Your access is blocked', Response::HTTP_BAD_REQUEST); - $data = array(); $validator = Validator::make($request->all(), [ 'description' => 'required', 'comment_id' => 'required' @@ -293,19 +291,42 @@ public function updateComment(Request $request, $id) ->where('user_id', $user->id)->first(); if ($comment) { $comment->description = $request->description; + $comment->edited_at = Carbon::now('UTC'); $comment->save(); return $this->successResponse($comment); } return $this->errorResponse('Invalid discussion id', Response::HTTP_BAD_REQUEST); } + public function deleteComment($id, $commentId) + { + $user = auth()->user()->load(['pagePermissions']); + if (Helper::isAccessBlocked($user, 'discussions')) + return $this->errorResponse('Your access is blocked', Response::HTTP_BAD_REQUEST); + + $comment = DiscussionComment::where('id', $commentId) + ->where('discussion_id', $id) + ->where(function ($query) use ($user) { + if ($user->role != 'admin') { + $query->where('user_id', $user->id); + } + }) + ->first(); + if ($comment) { + $comment->description = '

Comment deleted

'; + $comment->deleted_at = Carbon::now('UTC'); + $comment->save(); + return $this->metaSuccess(); + } + return $this->errorResponse('Invalid discussion id', Response::HTTP_BAD_REQUEST); + } + public function setVote(Request $request, $id) { $user = auth()->user()->load(['pagePermissions']); if (Helper::isAccessBlocked($user, 'discussions')) return $this->errorResponse('Your access is blocked', Response::HTTP_BAD_REQUEST); - $data = array(); $validator = Validator::make($request->all(), [ 'is_like' => 'required|boolean' ]); @@ -361,7 +382,6 @@ public function setPin(Request $request, $id) if (Helper::isAccessBlocked($user, 'discussions')) return $this->errorResponse('Your access is blocked', Response::HTTP_BAD_REQUEST); - $data = array(); $pinned = $this->discussionPinRepo->first(['discussion_id' => $id, 'user_id' => $user->id]); if ($pinned == null) { $this->discussionPinRepo->create(['discussion_id' => $id, 'user_id' => $user->id]); diff --git a/database/migrations/2022_11_17_004030_update_discussion_comment_table.php b/database/migrations/2022_11_17_004030_update_discussion_comment_table.php new file mode 100644 index 00000000..89072db6 --- /dev/null +++ b/database/migrations/2022_11_17_004030_update_discussion_comment_table.php @@ -0,0 +1,16 @@ +timestamp('edited_at')->nullable(); + $table->timestamp('deleted_at')->nullable(); + }); + } + public function down() { + // + } +} diff --git a/routes/api.php b/routes/api.php index d526b8b8..92fed092 100644 --- a/routes/api.php +++ b/routes/api.php @@ -228,6 +228,7 @@ Route::post('/new', [DiscussionController::class, 'postDiscussion']); Route::put('/{id}', [DiscussionController::class, 'updateDiscussion']); Route::delete('/{id}', [DiscussionController::class, 'deleteDiscussion']); + Route::delete('/{id}/comment/{commentId}', [DiscussionController::class, 'deleteComment']); Route::delete('/{id}/new', [DiscussionController::class, 'removeNewMark']); Route::post('/{id}/comment', [DiscussionController::class, 'createComment']); Route::put('/{id}/comment', [DiscussionController::class, 'updateComment']); From b4c1378697c54abc5dce2bafb1b33e8e2ac6f5e2 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 17 Nov 2022 14:11:26 -0500 Subject: [PATCH 124/162] # Time Format --- app/Console/Commands/CheckBallot.php | 5 +- app/Console/Commands/HistoricalData.php | 18 +- app/Console/Commands/KycReport.php | 4 +- app/Console/Commands/NodeInfo.php | 2 - app/Console/Commands/NotifCheck.php | 12 +- app/Console/Commands/PerkCheck.php | 16 +- app/Console/Kernel.php | 2 + .../Controllers/Api/V1/AdminController.php | 91 ++++----- .../Controllers/Api/V1/AuthController.php | 27 +-- .../Api/V1/DiscussionController.php | 2 +- .../Api/V1/NotificationController.php | 19 +- .../Controllers/Api/V1/PerkController.php | 176 +++++++++--------- .../Controllers/Api/V1/UserController.php | 45 ++--- app/Models/Ballot.php | 5 - app/Models/Discussion.php | 4 +- app/Models/User.php | 2 +- app/Services/ShuftiproCheck.php | 12 +- ...022_11_17_140007_update_table_ballot_2.php | 15 ++ ...022_11_17_141449_update_table_ballot_3.php | 16 ++ .../2022_11_17_145106_update_perk_2_table.php | 32 ++++ routes/api.php | 2 +- tests/TestCase.php | 5 +- 22 files changed, 279 insertions(+), 233 deletions(-) create mode 100644 database/migrations/2022_11_17_140007_update_table_ballot_2.php create mode 100644 database/migrations/2022_11_17_141449_update_table_ballot_3.php create mode 100644 database/migrations/2022_11_17_145106_update_perk_2_table.php diff --git a/app/Console/Commands/CheckBallot.php b/app/Console/Commands/CheckBallot.php index d4459aca..a1ed145d 100644 --- a/app/Console/Commands/CheckBallot.php +++ b/app/Console/Commands/CheckBallot.php @@ -42,8 +42,9 @@ public function handle() { $settings = Helper::getSettings(); $quorumRate = $settings['quorum_rate_ballot'] ?? 50; - $now = Carbon::now('UTC'); - $ballots = Ballot::with(['vote'])->where('status', 'active')->where('time_end', '<=', $now)->get(); + $ballots = Ballot::with(['vote'])->where('status', 'active') + ->where('time_end', '<=', Carbon::now('UTC')) + ->get(); foreach ($ballots as $ballot) { $vote = $ballot->vote; if ($vote->result_count == 0) { diff --git a/app/Console/Commands/HistoricalData.php b/app/Console/Commands/HistoricalData.php index a7627b13..855f0dcc 100644 --- a/app/Console/Commands/HistoricalData.php +++ b/app/Console/Commands/HistoricalData.php @@ -46,9 +46,9 @@ public function handle() $get_auction = 'casper-client get-auction-info '; $node_arg = '--node-address http://18.219.70.138:7777/rpc '; - $json = shell_exec($get_block.$node_arg); + $json = shell_exec($get_block . $node_arg); $json = json_decode($json); - $current_era = (int)($json->result->block->header->era_id ?? 0); + $current_era = (int) ($json->result->block->header->era_id ?? 0); $historic_era = DB::select(" SELECT era_id @@ -56,16 +56,16 @@ public function handle() ORDER BY era_id DESC LIMIT 1 "); - $historic_era = (int)($historic_era[0]->era_id ?? 0); - info('historic_era: '.$historic_era); + $historic_era = (int) ($historic_era[0]->era_id ?? 0); + info('historic_era: ' . $historic_era); $blocks_per_era = 100; $historic_block = $blocks_per_era * $historic_era; - info('historic_block: '.$historic_block); + info('historic_block: ' . $historic_block); $test_era = 0; $timestamp = ''; while ($test_era < $historic_era) { - $json = shell_exec($get_block.$node_arg.'-b '.$historic_block); + $json = shell_exec($get_block . $node_arg . '-b ' . $historic_block); $json = json_decode($json); $test_era = (int)($json->result->block->header->era_id ?? 0); $timestamp = $json->result->block->header->timestamp ?? ''; @@ -115,7 +115,7 @@ public function handle() if ($era_id == $historic_era) { // start timer - $start_time = (int)time(); + $start_time = (int) time(); // get auction info for this new detected era switch info($era_id.' '.$block_hash); @@ -395,12 +395,12 @@ public function handle() // DailyEarning garbage cleanup DailyEarning::where( 'created_at', - '<', + '<', Carbon::now('UTC')->subDays(90) )->delete(); // end timer - $end_time = (int)time(); + $end_time = (int) time(); info("Time spent on era: ".($end_time - $start_time)); } diff --git a/app/Console/Commands/KycReport.php b/app/Console/Commands/KycReport.php index fca59009..06c22f6c 100644 --- a/app/Console/Commands/KycReport.php +++ b/app/Console/Commands/KycReport.php @@ -45,8 +45,8 @@ public function __construct() public function handle() { // first do shufti temp records - $now = Carbon::now(); - $yesterday = Carbon::now()->subHours(12); + $now = Carbon::now('UTC'); + $yesterday = Carbon::now('UTC')->subHours(12); $records_temp = ShuftiproTemp::where('status', 'pending') ->where('created_at', '<=', $yesterday) ->limit(10) diff --git a/app/Console/Commands/NodeInfo.php b/app/Console/Commands/NodeInfo.php index 4e01ffd6..6349db7b 100644 --- a/app/Console/Commands/NodeInfo.php +++ b/app/Console/Commands/NodeInfo.php @@ -10,8 +10,6 @@ use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; -use Carbon\Carbon; - class NodeInfo extends Command { /** diff --git a/app/Console/Commands/NotifCheck.php b/app/Console/Commands/NotifCheck.php index 4fe87980..f1eb5062 100644 --- a/app/Console/Commands/NotifCheck.php +++ b/app/Console/Commands/NotifCheck.php @@ -39,9 +39,13 @@ public function __construct() */ public function handle() { - $now = Carbon::now()->format('Y-m-d'); + $now = Carbon::now('UTC')->format('Y-m-d'); // check notification waiting - $waitingNotification = Notification::where('status', 'waiting')->where('setting', 1)->where('start_date', '<=', $now)->where('end_date', '>=', $now)->get(); + $waitingNotification = Notification::where('status', 'waiting') + ->where('setting', 1) + ->where('start_date', '<=', $now) + ->where('end_date', '>=', $now) + ->get(); foreach ($waitingNotification as $data) { $data->status = 'active'; $data->visibility = 'visible'; @@ -49,7 +53,9 @@ public function handle() } // check notification expired - $expiredNotification = Notification::where('end_date', '<', $now)->where('setting', 1)->get(); + $expiredNotification = Notification::where('end_date', '<', $now) + ->where('setting', 1) + ->get(); foreach ($expiredNotification as $data) { $data->status = 'expired'; $data->visibility = 'hidden'; diff --git a/app/Console/Commands/PerkCheck.php b/app/Console/Commands/PerkCheck.php index 6d4a47eb..5f955fe6 100644 --- a/app/Console/Commands/PerkCheck.php +++ b/app/Console/Commands/PerkCheck.php @@ -39,17 +39,25 @@ public function __construct() */ public function handle() { - $now = Carbon::now()->format('Y-m-d'); + $now = Carbon::now('UTC'); + // check perk waiting - $waitingPerks = Perk::where('status', 'waiting')->where('setting', 1)->where('start_date', '<=', $now)->where('end_date', '>=', $now)->get(); + $waitingPerks = Perk::where('status', 'waiting') + ->where('setting', 1) + ->where('time_begin', '<=', $now) + ->where('time_end', '>=', $now) + ->get(); foreach ($waitingPerks as $perk) { $perk->status = 'active'; $perk->visibility = 'visible'; $perk->save(); } - + // check perk expired - $expiredPerks = Perk::where('end_date', '<', $now)->where('setting', 1)->where('status', '!=', 'expired')->get(); + $expiredPerks = Perk::where('time_end', '<', $now) + ->where('setting', 1) + ->where('status', '!=', 'expired') + ->get(); foreach ($expiredPerks as $perk) { $perk->status = 'expired'; $perk->visibility = 'hidden'; diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 348127cb..521159f7 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -60,9 +60,11 @@ protected function schedule(Schedule $schedule) ->everyFiveMinutes() ->runInBackground(); + /* $schedule->command('refresh:address') ->everyFiveMinutes() ->runInBackground(); + */ $schedule->command('kyc:report') ->dailyAt('10:02') ->runInBackground(); diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index 8b38c31a..d35bf27b 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -491,7 +491,7 @@ public function revokeUser($id) if ($user && $user->profile->status == 'approved' && $user->profile->extra_status != 'Suspended') { $user->profile->extra_status = 'Suspended'; $user->profile->revoke_reason = 'Admin action'; - $user->profile->revoke_at = now(); + $user->profile->revoke_at = Carbon::now('UTC'); $user->profile->reactivation_reason = null; $user->profile->reactivation_requested = null; $user->profile->reactivation_requested_at = null; @@ -567,7 +567,7 @@ public function approveReinstatement(Request $request) $historyRecord->reactivation_reason = $reactivation_reason; $historyRecord->reactivation_requested_at = $reactivation_requested_at; $historyRecord->decision = true; - $historyRecord->decision_at = now(); + $historyRecord->decision_at = Carbon::now('UTC'); $historyRecord->save(); } @@ -617,7 +617,7 @@ public function rejectReinstatement(Request $request) $historyRecord->reactivation_reason = $reactivation_reason; $historyRecord->reactivation_requested_at = $reactivation_requested_at; $historyRecord->decision = false; - $historyRecord->decision_at = now(); + $historyRecord->decision_at = Carbon::now('UTC'); $historyRecord->save(); } @@ -1126,46 +1126,39 @@ public function submitBallot(Request $request) 'start_time' => 'required', 'end_date' => 'required', 'end_time' => 'required', + 'timezone' => 'required' ]); if ($validator->fails()) { return $this->validateResponse($validator->errors()); } - $time = $request->time; - $timeUnit = $request->time_unit; - $mins = 0; + $timezone = $request->timezone; + + $startTime = $request->start_date . ' ' . $request->start_time; + $startTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $startTime, $timezone); + $startTimeCarbon->setTimezone('UTC'); - if ($timeUnit == 'minutes') { - $mins = $time; - } else if ($timeUnit == 'hours') { - $mins = $time * 60; - } else if ($timeUnit == 'days') { - $mins = $time * 60 * 24; - } - - $start = Carbon::createFromFormat("Y-m-d H:i:s", Carbon::now('UTC'), "UTC"); - $now = Carbon::now('UTC'); - $timeEnd = $start->addMinutes($mins); - $endTime = $request->end_date . ' ' . $request->end_time; - $endTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $endTime, 'EST'); + $endTime = $request->end_date . ' ' . $request->end_time; + $endTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $endTime, $timezone); $endTimeCarbon->setTimezone('UTC'); $ballot = new Ballot(); $ballot->user_id = $user->id; $ballot->title = $request->title; $ballot->description = $request->description; + $ballot->time_begin = $startTimeCarbon; $ballot->time_end = $endTimeCarbon; $ballot->start_date = $request->start_date; $ballot->start_time = $request->start_time; $ballot->end_date = $request->end_date; $ballot->end_time = $request->end_time; $ballot->status = 'active'; - $ballot->created_at = $now; + $ballot->timezone = $timezone; $ballot->save(); $vote = new Vote(); - $vote->ballot_id = $ballot->id; + $vote->ballot_id = $ballot->id; $vote->save(); if ($request->hasFile('files')) { @@ -1235,20 +1228,20 @@ public function editBallot($id, Request $request) 'start_time' => 'required', 'end_date' => 'required', 'end_time' => 'required', + 'timezone' => 'required' ]); if ($validator->fails()) { return $this->validateResponse($validator->errors()); } - $time = $request->time; - $timeUnit = $request->time_unit; - $ballot = Ballot::where('id', $id)->first(); - + $ballot = Ballot::where('id', $id)->first(); if (!$ballot) { return $this->errorResponse('Not found ballot', Response::HTTP_BAD_REQUEST); } + $timezone = $request->timezone; + if ($request->title) { $ballot->title = $request->title; } @@ -1257,17 +1250,21 @@ public function editBallot($id, Request $request) $ballot->description = $request->description; } + $startTime = $request->start_date . ' ' . $request->start_time; + $startTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $startTime, $timezone); + $startTimeCarbon->setTimezone('UTC'); + $endTime = $request->end_date . ' ' . $request->end_time; - $endTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $endTime, 'EST'); + $endTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $endTime, $timezone); $endTimeCarbon->setTimezone('UTC'); - - $now = Carbon::now('UTC'); - $ballot->created_at = $now; + + $ballot->time_begin = $startTimeCarbon; $ballot->time_end = $endTimeCarbon; $ballot->start_date = $request->start_date; $ballot->start_time = $request->start_time; $ballot->end_date = $request->end_date; $ballot->end_time = $request->end_time; + $ballot->timezone = $request->timezone; $ballot->save(); if ($request->hasFile('files')) { @@ -1335,32 +1332,18 @@ public function getBallots(Request $request) $status = $request->status; $sort_key = $request->sort_key ?? 'ballot.id'; $sort_direction = $request->sort_direction ?? 'desc'; - $now = Carbon::now('EST'); - $startDate = $now->format('Y-m-d'); - $startTime = $now->format('H:i:s'); - + $now = Carbon::now('UTC'); + if ($status == 'active') { $ballots = Ballot::with(['user', 'vote']) ->where('ballot.status', 'active') - ->where(function ($query) use ($startDate, $startTime) { - $query->where('start_date', '<', $startDate) - ->orWhere(function ($query) use ($startDate, $startTime) { - $query->where('start_date', $startDate) - ->where('start_time', '<=', $startTime); - }); - }) + ->where('ballot.time_begin', '<=', $now) ->orderBy($sort_key, $sort_direction) ->paginate($limit); } else if ($status && $status == 'scheduled') { $ballots = Ballot::with(['user', 'vote']) ->where('ballot.status', 'active') - ->where(function ($query) use ($startDate, $startTime) { - $query->where('start_date', '>', $startDate) - ->orWhere(function ($query) use ($startDate, $startTime) { - $query->where('start_date', $startDate) - ->where('start_time', '>', $startTime); - }); - }) + ->where('ballot.time_begin', '>', $now) ->orderBy($sort_key, $sort_direction) ->paginate($limit); } else if ($status && $status != 'active' && $status != 'scheduled') { @@ -1403,7 +1386,7 @@ public function cancelBallot($id) ); } - $ballot->time_end = now(); + $ballot->time_end = Carbon::now('UTC'); $ballot->status = 'cancelled'; $ballot->save(); return $this->metaSuccess(); @@ -1548,7 +1531,7 @@ public function inviteSubAdmin(Request $request) $verify->email = $request->email; $verify->type = VerifyUser::TYPE_INVITE_ADMIN; $verify->code = $code; - $verify->created_at = now(); + $verify->created_at = Carbon::now('UTC'); $verify->save(); $admin = User::create([ @@ -1698,7 +1681,7 @@ public function resendLink(Request $request, $id) $verify->email = $admin->email; $verify->type = VerifyUser::TYPE_INVITE_ADMIN; $verify->code = $code; - $verify->created_at = now(); + $verify->created_at = Carbon::now('UTC'); $verify->save(); Mail::to($admin->email)->send(new InvitationMail($inviteUrl)); @@ -1738,7 +1721,7 @@ public function resetSubAdminResetPassword(Request $request, $id) $verify->email = $admin->email; $verify->type = VerifyUser::TYPE_RESET_PASSWORD; $verify->code = $code; - $verify->created_at = now(); + $verify->created_at = Carbon::now('UTC'); $verify->save(); Mail::to($admin->email)->send(new ResetPasswordMail($resetUrl)); @@ -1824,7 +1807,7 @@ public function approveIntakeUser($id) ->first(); if ($user && $user->letter_file) { - $user->letter_verified_at = now(); + $user->letter_verified_at = Carbon::now('UTC'); $user->save(); $emailerData = EmailerHelper::getEmailerData(); @@ -1867,7 +1850,7 @@ public function resetIntakeUser($id, Request $request) if ($user) { $user->letter_verified_at = null; $user->letter_file = null; - $user->letter_rejected_at = now(); + $user->letter_rejected_at = Carbon::now('UTC'); $user->save(); $message = trim($request->get('message')); @@ -2122,7 +2105,7 @@ public function approveDocument($id) ->first(); if ($user && $user->profile) { - $user->profile->document_verified_at = now(); + $user->profile->document_verified_at = Carbon::now('UTC'); $user->profile->save(); return $this->metaSuccess(); } diff --git a/app/Http/Controllers/Api/V1/AuthController.php b/app/Http/Controllers/Api/V1/AuthController.php index 0237b1d6..54b5a09f 100644 --- a/app/Http/Controllers/Api/V1/AuthController.php +++ b/app/Http/Controllers/Api/V1/AuthController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\V1; +use Carbon\Carbon; use App\Console\Helper; use App\Http\Controllers\Controller; @@ -128,11 +129,11 @@ public function login(LoginRequest $request) $verify->email = $user->email; $verify->type = VerifyUser::TYPE_LOGIN_TWO_FA; $verify->code = $code; - $verify->created_at = now(); + $verify->created_at = Carbon::now('UTC'); $verify->save(); Mail::to($user)->send(new LoginTwoFA($code)); } - $user->last_login_at = now(); + $user->last_login_at = Carbon::now('UTC'); $user->last_login_ip_address = request()->ip(); $user->save(); $ipHistory = new IpHistory(); @@ -160,7 +161,7 @@ public function registerEntity(RegisterEntityRequest $request) $data = $request->all(); $data['password'] = bcrypt($request->password); - $data['last_login_at'] = now(); + $data['last_login_at'] = Carbon::now('UTC'); $data['type'] = User::TYPE_ENTITY; $user = $this->userRepo->create($data); $code = generateString(7); @@ -171,13 +172,13 @@ public function registerEntity(RegisterEntityRequest $request) ], [ 'code' => $code, - 'created_at' => now() + 'created_at' => Carbon::now('UTC') ] ); Mail::to($user->email)->send(new UserVerifyMail($code)); DB::commit(); $user->pending_node = 1; - $user->last_login_at = now(); + $user->last_login_at = Carbon::now('UTC'); $user->last_login_ip_address = request()->ip(); $user->save(); @@ -206,7 +207,7 @@ public function registerIndividual(RegisterIndividualRequest $request) $data = $request->all(); $data['password'] = bcrypt($request->password); - $data['last_login_at'] = now(); + $data['last_login_at'] = Carbon::now('UTC'); $data['type'] = User::TYPE_INDIVIDUAL; $user = $this->userRepo->create($data); $code = generateString(7); @@ -217,13 +218,13 @@ public function registerIndividual(RegisterIndividualRequest $request) ], [ 'code' => $code, - 'created_at' => now(), + 'created_at' => Carbon::now('UTC'), ] ); Mail::to($user->email)->send(new UserVerifyMail($code)); DB::commit(); $user->pending_node = 1; - $user->last_login_at = now(); + $user->last_login_at = Carbon::now('UTC'); $user->last_login_ip_address = request()->ip(); $user->save(); @@ -253,7 +254,7 @@ public function verifyEmail(Request $request) ['code' => $request->code, 'email' => $user->email] ); if ($this->checCode($verifyUser)) { - $user->update(['email_verified_at' => now()]); + $user->update(['email_verified_at' => Carbon::now('UTC')]); $verifyUser->delete(); $emailerData = EmailerHelper::getEmailerData(); EmailerHelper::triggerUserEmail($user->email, 'Welcome to the Casper', $emailerData, $user); @@ -294,7 +295,7 @@ public function sendResetLinkEmail(SendResetPasswordMailRequeslRequest $request) ], [ 'code' => $code, - 'created_at' => now(), + 'created_at' => Carbon::now('UTC'), ] ); if ($passwordReset) { @@ -356,7 +357,7 @@ public function resendVerifyEmail(Request $request) ], [ 'code' => $code, - 'created_at' => now() + 'created_at' => Carbon::now('UTC') ] ); if ($userVerify) { @@ -392,7 +393,7 @@ public function registerSubAdmin(Request $request) $user->first_name = $request->first_name; $user->last_name = $request->last_name; $user->password = bcrypt($request->password); - $user->last_login_at = now(); + $user->last_login_at = Carbon::now('UTC'); $user->last_login_ip_address = request()->ip(); $user->member_status = 'active'; $user->save(); @@ -422,6 +423,6 @@ public function createTokenFromUser($user, $info = []) */ private function checCode($verifyUser) { - return ($verifyUser && $verifyUser->created_at >= now()->subMinutes(VerifyUser::TOKEN_LIFETIME)); + return ($verifyUser && $verifyUser->created_at >= Carbon::now('UTC')->subMinutes(VerifyUser::TOKEN_LIFETIME)); } } \ No newline at end of file diff --git a/app/Http/Controllers/Api/V1/DiscussionController.php b/app/Http/Controllers/Api/V1/DiscussionController.php index 9ece9b02..ec334a7b 100644 --- a/app/Http/Controllers/Api/V1/DiscussionController.php +++ b/app/Http/Controllers/Api/V1/DiscussionController.php @@ -397,7 +397,7 @@ public function removeNewMark(Request $request, $id) if (Helper::isAccessBlocked($user, 'discussions')) return $this->errorResponse('Your access is blocked', Response::HTTP_BAD_REQUEST); - $this->discussionRemoveNewRepo->deleteConditions([['created_at', '<=', Carbon::now()->subDays(3)]]); + $this->discussionRemoveNewRepo->deleteConditions([['created_at', '<=', Carbon::now('UTC')->subDays(3)]]); $this->discussionRemoveNewRepo->create(['discussion_id' => $id, 'user_id' => $user->id]); return $this->metaSuccess(); diff --git a/app/Http/Controllers/Api/V1/NotificationController.php b/app/Http/Controllers/Api/V1/NotificationController.php index fd270335..eea65c61 100644 --- a/app/Http/Controllers/Api/V1/NotificationController.php +++ b/app/Http/Controllers/Api/V1/NotificationController.php @@ -62,15 +62,15 @@ public function dismiss($id) $notificationView = new notificationView(); $notificationView->user_id = $user->id; $notificationView->notification_id = $id; - $notificationView->first_view_at = now(); - $notificationView->dismissed_at = now(); + $notificationView->first_view_at = Carbon::now('UTC'); + $notificationView->dismissed_at = Carbon::now('UTC'); $notificationView->save(); $notification->total_views = $notification->total_views + 1; $notification->save(); } else { if (!$notificationView->dismissed_at) { - $notificationView->dismissed_at = now(); + $notificationView->dismissed_at = Carbon::now('UTC'); $notificationView->save(); } } @@ -90,8 +90,8 @@ public function clickCTA($id) $notificationView = new notificationView(); $notificationView->user_id = $user->id; $notificationView->notification_id = $id; - $notificationView->first_view_at = now(); - $notificationView->cta_click_at = now(); + $notificationView->first_view_at = Carbon::now('UTC'); + $notificationView->cta_click_at = Carbon::now('UTC'); $notificationView->cta_click_count = $notificationView->cta_click_count + 1; $notificationView->save(); @@ -99,7 +99,7 @@ public function clickCTA($id) $notification->save(); } else { if (!$notificationView->cta_click_at) { - $notificationView->cta_click_at = now(); + $notificationView->cta_click_at = Carbon::now('UTC'); } $notificationView->cta_click_count = $notificationView->cta_click_count + 1; $notificationView->save(); @@ -120,7 +120,7 @@ public function updateView($id) $notificationView = new notificationView(); $notificationView->user_id = $user->id; $notificationView->notification_id = $id; - $notificationView->first_view_at = now(); + $notificationView->first_view_at = Carbon::now('UTC'); $notificationView->save(); $notification->total_views = $notification->total_views + 1; @@ -203,7 +203,7 @@ public function createNotification(Request $request) return $this->validateResponse($validator->errors()); } - $now = Carbon::now()->format('Y-m-d'); + $now = Carbon::now('UTC')->format('Y-m-d'); $notification = new Notification(); $startDate = $request->start_date; $endDate = $request->end_date; @@ -295,7 +295,7 @@ public function updateNotification(Request $request, $id) return $this->validateResponse($validator->errors()); } - $now = Carbon::now()->format('Y-m-d'); + $now = Carbon::now('UTC')->format('Y-m-d'); $notification = Notification::where('id', $id)->first(); if (!$notification) { return $this->errorResponse('Not found notification', Response::HTTP_BAD_REQUEST); @@ -364,7 +364,6 @@ public function getNotificationDetail($id) if (!$notification) { return $this->errorResponse('Not found notification', Response::HTTP_BAD_REQUEST); } - return $this->successResponse($notification); } } diff --git a/app/Http/Controllers/Api/V1/PerkController.php b/app/Http/Controllers/Api/V1/PerkController.php index e3986122..80bce100 100644 --- a/app/Http/Controllers/Api/V1/PerkController.php +++ b/app/Http/Controllers/Api/V1/PerkController.php @@ -31,23 +31,30 @@ public function createPerk(Request $request) { 'start_time' => 'required|nullable|date_format:H:i:s', 'end_time' => 'required|nullable|date_format:H:i:s', 'setting' => 'required|in:0,1', + 'timezone' => 'required' ]); if ($validator->fails()) { return $this->validateResponse($validator->errors()); } $user = auth()->user(); - $now = Carbon::now()->format('Y-m-d'); - $perk = new Perk(); - $startDate = $request->start_date; - $endDate = $request->end_date; + + $timezone = $request->timezone; + + $startTime = $request->start_date . ' ' . $request->start_time; + $startTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $startTime, $timezone); + $startTimeCarbon->setTimezone('UTC'); + + $endTime = $request->end_date . ' ' . $request->end_time; + $endTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $endTime, $timezone); + $endTimeCarbon->setTimezone('UTC'); + $setting = $request->setting; - $visibility = 'hidden'; - $status = 'inactive'; - if ($startDate && $endDate && $startDate > $endDate) { + if ($startTimeCarbon->gt($endTimeCarbon)) { return $this->errorResponse('End date must greater than start date', Response::HTTP_BAD_REQUEST); } + $perk = new Perk(); $perk->user_id = $user->id; $perk->title = $request->title; $perk->content = $request->content; @@ -56,16 +63,16 @@ public function createPerk(Request $request) { $perk->end_date = $request->end_date; $perk->start_time = $request->start_time; $perk->end_time = $request->end_time; - $perk->setting = $request->setting; + $perk->setting = $setting; + $perk->timezone = $timezone; + $perk->time_begin = $startTimeCarbon; + $perk->time_end = $endTimeCarbon; $filenameWithExt = $request->file('image')->getClientOriginalName(); - //Get just filename $filename = pathinfo($filenameWithExt, PATHINFO_FILENAME); - // Get just ext $extension = $request->file('image')->getClientOriginalExtension(); - $filenamehash = md5(Str::random(10) . '_' . (string)time()); - // Filename to store + $filenamehash = md5(Str::random(10) . '_' . (string) time()); $fileNameToStore = $filenamehash . '.' . $extension; // S3 file upload @@ -77,39 +84,30 @@ public function createPerk(Request $request) { 'secret' => getenv('AWS_SECRET_ACCESS_KEY'), ], ]); - $s3result = $S3->putObject([ 'Bucket' => getenv('AWS_BUCKET'), 'Key' => 'client_uploads/' . $fileNameToStore, 'SourceFile' => $request->file('image') ]); - // $ObjectURL = 'https://'.getenv('AWS_BUCKET').'.s3.amazonaws.com/client_uploads/'.$fileNameToStore; $ObjectURL = $s3result['ObjectURL'] ?? getenv('SITE_URL') . '/not-found'; $perk->image = $ObjectURL; // check visibility and status + $now = Carbon::now('UTC'); + $visibility = 'hidden'; + $status = 'inactive'; if ($setting == 1) { - if ($startDate && $endDate && ($now >= $startDate && $now <= $endDate)) { - $visibility = 'visible'; - $status = 'active'; - } - if (!$startDate && !$endDate) { - $visibility = 'visible'; - $status = 'active'; - } - if ($endDate && $endDate >= $now) { - $visibility = 'visible'; - $status = 'active'; - } - if ($startDate && $startDate > $now) { - $visibility = 'hidden'; - $status = 'waiting'; - } - if ($startDate && $startDate <= $now) { - $visibility = 'visible'; - $status = 'active'; - } + if ($now >= $startTimeCarbon && $now <= $endTimeCarbon) { + $visibility = 'visible'; + $status = 'active'; + } else if ($now < $startTimeCarbon) { + $visibility = 'hidden'; + $status = 'waiting'; + } else if ($now > $endTimeCarbon) { + $visibility = 'hidden'; + $status = 'expired'; + } } else { $visibility = 'hidden'; $status = 'inactive'; @@ -120,27 +118,6 @@ public function createPerk(Request $request) { return $this->successResponse($perk); } - public function getPerksAdmin(Request $request) - { - $limit = $request->limit ?? 50; - $sort_key = $request->sort_key ?? 'end_date'; - $sort_direction = $request->sort_direction ?? 'desc'; - if (isset($request->setting)) { - $perks = Perk::where('setting', $request->setting)->orderBy($sort_key, $sort_direction)->paginate($limit); - } else { - $perks = Perk::orderBy($sort_key, $sort_direction)->paginate($limit); - } - return $this->successResponse($perks); - } - - public function getPerkDetailAdmin($id) - { - $perk = Perk::where('id', $id)->first(); - if (!$perk) { - return $this->errorResponse('Not found perk', Response::HTTP_BAD_REQUEST); - } - return $this->successResponse($perk); - } public function updatePerk(Request $request, $id) { $data = $request->all(); @@ -154,26 +131,33 @@ public function updatePerk(Request $request, $id) 'start_time' => 'required|nullable|date_format:H:i:s', 'end_time' => 'required|nullable|date_format:H:i:s', 'setting' => 'nullable|in:0,1', + 'timezone' => 'required' ]); if ($validator->fails()) { return $this->validateResponse($validator->errors()); } $user = auth()->user(); - $now = Carbon::now()->format('Y-m-d'); + $perk = Perk::where('id', $id)->first(); if (!$perk) { return $this->errorResponse('Not found perk', Response::HTTP_BAD_REQUEST); } - $startDate = array_key_exists('start_date', $data) ? $request->start_date : $perk->start_date; - $endDate = array_key_exists('end_date', $data) ? $request->end_date : $perk->end_date; - $setting = isset($request->setting) ? $request->setting : $perk->setting; - $visibility = 'hidden'; - $status = 'inactive'; - if ($startDate && $endDate && $startDate > $endDate) { + $timezone = $request->timezone; + + $startTime = $request->start_date . ' ' . $request->start_time; + $startTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $startTime, $timezone); + $startTimeCarbon->setTimezone('UTC'); + + $endTime = $request->end_date . ' ' . $request->end_time; + $endTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $endTime, $timezone); + $endTimeCarbon->setTimezone('UTC'); + + if ($startTimeCarbon->gt($endTimeCarbon)) { return $this->errorResponse('End date must greater than start date', Response::HTTP_BAD_REQUEST); } + if ($request->title) { $perk->title = $request->title; } @@ -185,10 +169,14 @@ public function updatePerk(Request $request, $id) $perk->end_date = $request->end_date; $perk->start_time = $request->start_time; $perk->end_time = $request->end_time; - + $perk->timezone = $timezone; + $perk->time_begin = $startTimeCarbon; + $perk->time_end = $endTimeCarbon; + if (isset($request->setting)) { $perk->setting = $request->setting; } + if ($request->hasFile('image')) { $extension = $request->file('image')->getClientOriginalExtension(); $filenamehash = md5(Str::random(10) . '_' . (string)time()); @@ -216,31 +204,21 @@ public function updatePerk(Request $request, $id) } // check visibility and status - if ($setting == 1) { - if ($startDate && $endDate && ($now >= $startDate && $now <= $endDate)) { - $visibility = 'visible'; - $status = 'active'; - } - if (!$startDate && !$endDate) { - $visibility = 'visible'; - $status = 'active'; - } - if ($endDate && $endDate >= $now) { - $visibility = 'visible'; - $status = 'active'; - } - if ($endDate && $endDate < $now) { - $visibility = 'hidden'; - $status = 'expired'; - } - if ($startDate && $startDate > $now) { - $visibility = 'hidden'; - $status = 'waiting'; - } - if ($startDate && $startDate <= $now) { - $visibility = 'visible'; - $status = 'active'; - } + $visibility = 'hidden'; + $status = 'inactive'; + $setting = $perk->setting; + $now = Carbon::now('UTC'); + if ($setting == 1) { + if ($now >= $startTimeCarbon && $now <= $endTimeCarbon) { + $visibility = 'visible'; + $status = 'active'; + } else if ($now < $startTimeCarbon) { + $visibility = 'hidden'; + $status = 'waiting'; + } else if ($now > $endTimeCarbon) { + $visibility = 'hidden'; + $status = 'expired'; + } } else { $visibility = 'hidden'; $status = 'inactive'; @@ -251,6 +229,28 @@ public function updatePerk(Request $request, $id) return $this->successResponse($perk); } + public function getPerksAdmin(Request $request) + { + $limit = $request->limit ?? 50; + $sort_key = $request->sort_key ?? 'end_date'; + $sort_direction = $request->sort_direction ?? 'desc'; + if (isset($request->setting)) { + $perks = Perk::where('setting', $request->setting)->orderBy($sort_key, $sort_direction)->paginate($limit); + } else { + $perks = Perk::orderBy($sort_key, $sort_direction)->paginate($limit); + } + return $this->successResponse($perks); + } + + public function getPerkDetailAdmin($id) + { + $perk = Perk::where('id', $id)->first(); + if (!$perk) { + return $this->errorResponse('Not found perk', Response::HTTP_BAD_REQUEST); + } + return $this->successResponse($perk); + } + public function deletePerk($id) { PerkResult::where('perk_id', $id)->delete(); diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 2174774d..5f53e03b 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -874,7 +874,7 @@ public function changeEmail(ChangeEmailRequest $request) ], [ 'code' => $code, - 'created_at' => now() + 'created_at' => Carbon::now('UTC') ] ); @@ -1283,7 +1283,7 @@ public function verifyFileCasperSigner2(VerifyFileCasperSignerRequest $request) $userAddress->user_id = $user->id; $userAddress->public_address_node = $public_validator_key; $userAddress->signed_file = $ObjectURL; - $userAddress->node_verified_at = now(); + $userAddress->node_verified_at = Carbon::now('UTC'); $userAddress->save(); $emailerData = EmailerHelper::getEmailerData(); @@ -1373,11 +1373,11 @@ public function verifyFileCasperSigner(VerifyFileCasperSignerRequest $request) $user->signed_file = $ObjectURL; $user->has_verified_address = 1; - $user->node_verified_at = now(); + $user->node_verified_at = Carbon::now('UTC'); $user->save(); $userAddress->signed_file = $ObjectURL; - $userAddress->node_verified_at = now(); + $userAddress->node_verified_at = Carbon::now('UTC'); $userAddress->save(); $emailerData = EmailerHelper::getEmailerData(); @@ -1568,32 +1568,19 @@ public function getVotes(Request $request) ); } - $now = Carbon::now('EST'); + $now = Carbon::now('UTC'); + $startDate = $now->format('Y-m-d'); $startTime = $now->format('H:i:s'); if ($status == 'active') { $query = Ballot::where('status', 'active') - ->where(function ($query) use ($startDate, $startTime) { - $query->where('start_date', '<', $startDate) - ->orWhere(function ($query) use ($startDate, $startTime) { - $query->where('start_date', $startDate) - ->where('start_time', '<=', $startTime); - }); - }); + ->where('time_begin', '<=', $now); } - else if ($status == 'scheduled') { $query = Ballot::where('status', 'active') - ->where(function ($query) use ($startDate, $startTime) { - $query->where('start_date', '>', $startDate) - ->orWhere(function ($query) use ($startDate, $startTime) { - $query->where('start_date', $startDate) - ->where('start_time', '>', $startTime); - }); - }); + ->where('time_begin', '>', $now); } - else { $query = Ballot::where('status', '<>', 'active'); } @@ -1657,7 +1644,7 @@ public function requestReactivation(Request $request) ) { $user->profile->reactivation_reason = $reactivation_reason; $user->profile->reactivation_requested = true; - $user->profile->reactivation_requested_at = now(); + $user->profile->reactivation_requested_at = Carbon::now('UTC'); $user->profile->save(); } @@ -1943,7 +1930,7 @@ public function vote($id, Request $request) return $this->metaSuccess(); } else { $voteResult->type = $vote; - $voteResult->updated_at = now(); + $voteResult->updated_at = Carbon::now('UTC'); if ($vote == 'for') { $ballot->vote->for_value = $ballot->vote->for_value + 1; @@ -1953,7 +1940,7 @@ public function vote($id, Request $request) $ballot->vote->against_value = $ballot->vote->against_value + 1; } - $ballot->vote->updated_at = now(); + $ballot->vote->updated_at = Carbon::now('UTC'); $ballot->vote->save(); $voteResult->save(); } @@ -1972,7 +1959,7 @@ public function vote($id, Request $request) } $ballot->vote->result_count = $ballot->vote->result_count + 1; - $ballot->vote->updated_at = now(); + $ballot->vote->updated_at = Carbon::now('UTC'); $ballot->vote->save(); } return $this->metaSuccess(); @@ -2557,7 +2544,7 @@ function ($query) use ($emailParam) { $verify->code = $codeCurrentEmail; $verify->email = $currentEmail; $verify->type = VerifyUser::TYPE_CANCEL_EMAIL; - $verify->created_at = now(); + $verify->created_at = Carbon::now('UTC'); $verify->save(); // new email @@ -2594,7 +2581,7 @@ function ($query) use ($emailParam) { $verify->email = $newEmail; $verify->code = $codeNewEmail; $verify->type = VerifyUser::TYPE_CONFIRM_EMAIL; - $verify->created_at = now(); + $verify->created_at = Carbon::now('UTC'); $verify->save(); } $user->save(); @@ -2701,7 +2688,7 @@ public function resend2FA() $verify->email = $user->email; $verify->type = VerifyUser::TYPE_LOGIN_TWO_FA; $verify->code = $code; - $verify->created_at = now(); + $verify->created_at = Carbon::now('UTC'); $verify->save(); Mail::to($user)->send(new LoginTwoFA($code)); return $this->metaSuccess(); @@ -2970,7 +2957,7 @@ public function getEarningByNode($node) LIMIT 1 "); $nodeInfo = $nodeInfo[0] ?? null; - + // Calc earning $one_day_ago = Carbon::now('UTC')->subHours(24); $daily_earningObject = DB::select(" diff --git a/app/Models/Ballot.php b/app/Models/Ballot.php index 0ef47417..27308150 100644 --- a/app/Models/Ballot.php +++ b/app/Models/Ballot.php @@ -13,11 +13,6 @@ class Ballot extends Model protected $table = 'ballot'; protected $guarded = []; - public function getTimeEndAttribute($value) - { - return Carbon::parse($value); - } - public function user() { return $this->belongsTo('App\Models\User', 'user_id', 'id'); diff --git a/app/Models/Discussion.php b/app/Models/Discussion.php index c0e06bb7..bb4f18c7 100644 --- a/app/Models/Discussion.php +++ b/app/Models/Discussion.php @@ -31,8 +31,8 @@ public function getIsNewAttribute() 'user_id' => $user->id, 'discussion_id' => $this->id ])->first() == null; - $notOld = Carbon::now()->diffInDays(Carbon::parse($this->created_at)) < 3; - return $notOld && $notRemoved; + $notOld = Carbon::now('UTC')->diffInDays(Carbon::parse($this->created_at)) < 3; + return $notOld && $notRemoved; } public function getTotalPinnedAttribute() diff --git a/app/Models/User.php b/app/Models/User.php index c0b07b12..6a44ea77 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -202,7 +202,7 @@ public function nodeInfo() { public function getNewThreadsAttribute() { $removedNews = DiscussionRemoveNew::where(['user_id' => $this->id])->pluck('discussion_id'); $count = Discussion::whereNotIn('id', $removedNews) - ->whereDate('created_at', '>', Carbon::now()->subDays(3)) + ->whereDate('created_at', '>', Carbon::now('UTC')->subDays(3)) ->count(); return $count; } diff --git a/app/Services/ShuftiproCheck.php b/app/Services/ShuftiproCheck.php index ca03d253..a722265c 100644 --- a/app/Services/ShuftiproCheck.php +++ b/app/Services/ShuftiproCheck.php @@ -2,6 +2,8 @@ namespace App\Services; +use Carbon\Carbon; + use App\Models\Profile; use App\Models\Shuftipro; use App\Models\ShuftiproTemp; @@ -153,7 +155,7 @@ public function handleExisting($item) { $record->reviewed = $is_successful ? 1 : 0; // No need to review successful ones if ($status == 'approved') { - $record->manual_approved_at = now(); + $record->manual_approved_at = Carbon::now('UTC'); } if ($document_proof) { $record->document_proof = $document_proof; @@ -173,8 +175,8 @@ public function handleExisting($item) { $user = User::find($user_id); if ($user) { - $user->kyc_verified_at = now(); - $user->approve_at = now(); + $user->kyc_verified_at = Carbon::now('UTC'); + $user->approve_at = Carbon::now('UTC'); $user->save(); Mail::to($user->email)->send(new KYCApproved); @@ -354,8 +356,8 @@ public function handle($item) $user = User::find($user_id); if ($user) { - $user->kyc_verified_at = now(); - $user->approve_at = now(); + $user->kyc_verified_at = Carbon::now('UTC'); + $user->approve_at = Carbon::now('UTC'); $user->save(); Mail::to($user->email)->send(new KYCApproved); diff --git a/database/migrations/2022_11_17_140007_update_table_ballot_2.php b/database/migrations/2022_11_17_140007_update_table_ballot_2.php new file mode 100644 index 00000000..38beb2a2 --- /dev/null +++ b/database/migrations/2022_11_17_140007_update_table_ballot_2.php @@ -0,0 +1,15 @@ +string('timezone')->nullable(); + }); + } + public function down() { + // + } +} \ No newline at end of file diff --git a/database/migrations/2022_11_17_141449_update_table_ballot_3.php b/database/migrations/2022_11_17_141449_update_table_ballot_3.php new file mode 100644 index 00000000..77be3838 --- /dev/null +++ b/database/migrations/2022_11_17_141449_update_table_ballot_3.php @@ -0,0 +1,16 @@ +timestamp('time_begin')->nullable(); + }); + } + public function down() { + // + } +} diff --git a/database/migrations/2022_11_17_145106_update_perk_2_table.php b/database/migrations/2022_11_17_145106_update_perk_2_table.php new file mode 100644 index 00000000..a392724c --- /dev/null +++ b/database/migrations/2022_11_17_145106_update_perk_2_table.php @@ -0,0 +1,32 @@ +string('timezone')->nullable(); + $table->timestamp('time_begin')->nullable(); + $table->timestamp('time_end')->nullable(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +} diff --git a/routes/api.php b/routes/api.php index 92fed092..7bc3c515 100644 --- a/routes/api.php +++ b/routes/api.php @@ -25,7 +25,7 @@ | */ -//// REMOVE +/// REMOVE Route::get('/dev-verify-node/{address}', [AuthController::class, 'devVerifyNode'])->where('address', '[0-9a-zA-Z]+'); Route::namespace('Api')->middleware([])->group(function () { diff --git a/tests/TestCase.php b/tests/TestCase.php index ab8ee062..ee3d6d74 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,6 +2,7 @@ namespace Tests; +use Carbon\Carbon; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; use Illuminate\Foundation\Testing\DatabaseMigrations; @@ -66,7 +67,7 @@ public function addAdmin() { $user->last_name = 'Leap'; $user->email = 'ledgerleapllcadmin@gmail.com'; $user->password = Hash::make('Ledgerleapllcadmin111@'); - $user->email_verified_at = now(); + $user->email_verified_at = Carbon::now('UTC'); $user->type = 'active'; $user->role = 'admin'; $user->save(); @@ -112,7 +113,7 @@ public function addUser() { $user->pseudonym = $pseudonym; $user->telegram = $telegram; $user->type = User::TYPE_INDIVIDUAL; - $user->email_verified_at = now(); + $user->email_verified_at = Carbon::now('UTC'); $user->signature_request_id = 'TestSignatureRequestId'; $user->role = 'member'; $user->letter_file = 'LetterFileLink'; From b49a32ce1b14fbbfc2a78f1ee1345e9f5f96b7b5 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 17 Nov 2022 20:16:05 -0500 Subject: [PATCH 125/162] # Update Perk --- app/Http/Controllers/Api/V1/PerkController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Http/Controllers/Api/V1/PerkController.php b/app/Http/Controllers/Api/V1/PerkController.php index 80bce100..a84b8038 100644 --- a/app/Http/Controllers/Api/V1/PerkController.php +++ b/app/Http/Controllers/Api/V1/PerkController.php @@ -25,7 +25,7 @@ public function createPerk(Request $request) { 'title' => 'required|string|max:70', 'content' => 'required', 'action_link' => 'required|url', - 'image' => 'required|mimes:jpeg,jpg,png,gif|max:100000', + 'image' => 'required|mimes:pdf,jpeg,jpg,png,gif,txt,rtf|max:200000', 'start_date' => 'required|nullable|date_format:Y-m-d', 'end_date' => 'required|nullable|date_format:Y-m-d|after_or_equal:today', 'start_time' => 'required|nullable|date_format:H:i:s', @@ -125,7 +125,7 @@ public function updatePerk(Request $request, $id) 'title' => 'nullable|string|max:70', 'content' => 'nullable', 'action_link' => 'nullable|url', - 'image' => 'nullable|mimes:jpeg,jpg,png,gif|max:100000', + 'image' => 'nullable|mimes:pdf,jpeg,jpg,png,gif,txt,rtf|max:200000', 'start_date' => 'required|nullable', 'end_date' => 'required|nullable', 'start_time' => 'required|nullable|date_format:H:i:s', From f144ee0b9d7b73ba613633242519386d4cef2a14 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Thu, 17 Nov 2022 20:36:25 -0500 Subject: [PATCH 126/162] # Update --- app/Http/Controllers/Api/V1/UserController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 5f53e03b..ffe90298 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -2055,6 +2055,8 @@ public function getMembers(Request $request) 'users.id', 'users.pseudonym', 'users.created_at', + 'users.approve_at', + 'users.kyc_verified_at', 'user_addresses.node_verified_at', 'all_node_data2.public_key', 'all_node_data2.uptime', From 3c1c572917f9e2114f27a3ea9d97a227f571b2ad Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Fri, 18 Nov 2022 10:46:33 -0500 Subject: [PATCH 127/162] # File Upload Limit --- app/Http/Controllers/Api/V1/AdminController.php | 9 ++++++--- app/Http/Controllers/Api/V1/PerkController.php | 6 ++++-- app/Http/Controllers/Api/V1/UserController.php | 6 ++++-- app/Http/Controllers/Api/V1/VerificationController.php | 3 ++- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index d35bf27b..f77b8d32 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -1121,7 +1121,8 @@ public function submitBallot(Request $request) 'title' => 'required', 'description' => 'required', 'files' => 'array', - 'files.*' => 'file|max:10240|mimes:pdf,docx,doc,txt,rtf', + // 'files.*' => 'file|max:10240|mimes:pdf,docx,doc,txt,rtf', + 'files.*' => 'file|max:2048|mimes:pdf,docx,doc,txt,rtf', 'start_date' => 'required', 'start_time' => 'required', 'end_date' => 'required', @@ -1222,7 +1223,8 @@ public function editBallot($id, Request $request) 'title' => 'nullable', 'description' => 'nullable', 'files' => 'array', - 'files.*' => 'file|max:10240|mimes:pdf,docx,doc,txt,rtf', + // 'files.*' => 'file|max:10240|mimes:pdf,docx,doc,txt,rtf', + 'files.*' => 'file|max:2048|mimes:pdf,docx,doc,txt,rtf', 'file_ids_remove' => 'array', 'start_date' => 'required', 'start_time' => 'required', @@ -2309,7 +2311,8 @@ public function uploadMembershipFile(Request $request) try { // Validator $validator = Validator::make($request->all(), [ - 'file' => 'required|mimes:pdf,docx,doc,txt,rtf|max:5000', + // 'file' => 'required|mimes:pdf,docx,doc,txt,rtf|max:5000', + 'file' => 'required|mimes:pdf,docx,doc,txt,rtf|max:2048', ]); if ($validator->fails()) { diff --git a/app/Http/Controllers/Api/V1/PerkController.php b/app/Http/Controllers/Api/V1/PerkController.php index a84b8038..9fcdf742 100644 --- a/app/Http/Controllers/Api/V1/PerkController.php +++ b/app/Http/Controllers/Api/V1/PerkController.php @@ -25,7 +25,8 @@ public function createPerk(Request $request) { 'title' => 'required|string|max:70', 'content' => 'required', 'action_link' => 'required|url', - 'image' => 'required|mimes:pdf,jpeg,jpg,png,gif,txt,rtf|max:200000', + // 'image' => 'required|mimes:pdf,jpeg,jpg,png,gif,txt,rtf|max:200000', + 'image' => 'required|mimes:pdf,jpeg,jpg,png,gif,txt,rtf|max:2048', 'start_date' => 'required|nullable|date_format:Y-m-d', 'end_date' => 'required|nullable|date_format:Y-m-d|after_or_equal:today', 'start_time' => 'required|nullable|date_format:H:i:s', @@ -125,7 +126,8 @@ public function updatePerk(Request $request, $id) 'title' => 'nullable|string|max:70', 'content' => 'nullable', 'action_link' => 'nullable|url', - 'image' => 'nullable|mimes:pdf,jpeg,jpg,png,gif,txt,rtf|max:200000', + // 'image' => 'nullable|mimes:pdf,jpeg,jpg,png,gif,txt,rtf|max:200000', + 'image' => 'nullable|mimes:pdf,jpeg,jpg,png,gif,txt,rtf|max:2048', 'start_date' => 'required|nullable', 'end_date' => 'required|nullable', 'start_time' => 'required|nullable|date_format:H:i:s', diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index ffe90298..2e5b8a0b 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -900,7 +900,8 @@ public function uploadLetter(Request $request) try { // Validator $validator = Validator::make($request->all(), [ - 'file' => 'required|mimes:pdf,jpeg,jpg,png,txt,rtf|max:200000' + // 'file' => 'required|mimes:pdf,jpeg,jpg,png,txt,rtf|max:200000' + 'file' => 'required|mimes:pdf,jpeg,jpg,png,txt,rtf|max:2048' ]); if ($validator->fails()) { @@ -2000,7 +2001,8 @@ public function uploadAvatar(Request $request) try { // Validator $validator = Validator::make($request->all(), [ - 'avatar' => 'sometimes|mimes:jpeg,jpg,png,gif,webp|max:100000', + // 'avatar' => 'sometimes|mimes:jpeg,jpg,png,gif,webp|max:100000', + 'avatar' => 'sometimes|mimes:jpeg,jpg,png,gif,webp|max:2048', ]); if ($validator->fails()) { diff --git a/app/Http/Controllers/Api/V1/VerificationController.php b/app/Http/Controllers/Api/V1/VerificationController.php index cdbd2612..aa2de3f1 100644 --- a/app/Http/Controllers/Api/V1/VerificationController.php +++ b/app/Http/Controllers/Api/V1/VerificationController.php @@ -109,7 +109,8 @@ public function uploadDocument(Request $request) { // Validator $validator = Validator::make($request->all(), [ 'files' => 'array', - 'files.*' => 'file|max:100000|mimes:pdf,jpeg,jpg,png,txt,rtf' + // 'files.*' => 'file|max:100000|mimes:pdf,jpeg,jpg,png,txt,rtf' + 'files.*' => 'file|max:2048|mimes:pdf,jpeg,jpg,png,txt,rtf' ]); if ($validator->fails()) { return $this->validateResponse($validator->errors()); From b9be0802acf474e1acc60cf0a3856f925835f7ef Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Fri, 18 Nov 2022 12:17:41 -0500 Subject: [PATCH 128/162] # Check Address Validity --- app/Console/Helper.php | 11 ++++++++ .../Controllers/Api/V1/UserController.php | 27 ++++++++++++++++--- app/Services/NodeHelper.php | 3 ++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/app/Console/Helper.php b/app/Console/Helper.php index d4a7fed1..54fff78d 100644 --- a/app/Console/Helper.php +++ b/app/Console/Helper.php @@ -24,6 +24,17 @@ class Helper { + public static function checkAddressValidity($public_address_node) { + $current_era_id = self::getCurrentERAId(); + + $temp = AllNodeData2::select(['id']) + ->where('public_key', $public_address_node) + ->where('era_id', $current_era_id) + ->first(); + if ($temp) return true; + return false; + } + public static function getSettings() { $items = Setting::get(); $settings = []; diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index 2e5b8a0b..c3fa6b48 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -1047,15 +1047,22 @@ public function submitPublicAddress(SubmitPublicAddressRequest $request) } // Pool Check + /* $nodeHelper = new NodeHelper(); $addresses = $nodeHelper->getValidAddresses(); - if (!in_array($public_address, $addresses)) { return $this->errorResponse( __('The validator ID specified could not be found in the Casper validator pool'), Response::HTTP_BAD_REQUEST ); } + */ + if (!Helper::checkAddressValidity($public_address)) { + return $this->errorResponse( + __('The validator ID specified could not be found in the Casper validator pool'), + Response::HTTP_BAD_REQUEST + ); + } // Remove Other User's Same Address UserAddress::where('public_address_node', $public_address)->where('user_id', '!=', $user->id)->whereNull('node_verified_at')->delete(); @@ -1117,14 +1124,21 @@ public function checkValidatorAddress(SubmitPublicAddressRequest $request) ); } + /* $nodeHelper = new NodeHelper(); $addresses = $nodeHelper->getValidAddresses(); - if (!in_array($public_address, $addresses)) { return $this->successResponse( ['message' => __('The validator ID specified could not be found in the Casper validator pool')] ); } + */ + if (!Helper::checkAddressValidity($public_address)) { + return $this->successResponse( + ['message' => __('The validator ID specified could not be found in the Casper validator pool')] + ); + } + return $this->metaSuccess(); } @@ -1171,15 +1185,22 @@ public function checkPublicAddress(SubmitPublicAddressRequest $request) } // Pool Check + /* $nodeHelper = new NodeHelper(); $addresses = $nodeHelper->getValidAddresses(); - if (!in_array($public_address, $addresses)) { return $this->errorResponse( __('The validator ID specified could not be found in the Casper validator pool'), Response::HTTP_BAD_REQUEST ); } + */ + if (!Helper::checkAddressValidity($public_address)) { + return $this->errorResponse( + __('The validator ID specified could not be found in the Casper validator pool'), + Response::HTTP_BAD_REQUEST + ); + } return $this->metaSuccess(); } diff --git a/app/Services/NodeHelper.php b/app/Services/NodeHelper.php index d6cd9719..7a303094 100644 --- a/app/Services/NodeHelper.php +++ b/app/Services/NodeHelper.php @@ -234,7 +234,6 @@ public function discoverPeers() usleep(50000); } - // do last chunk $mh = curl_multi_init(); $ch = array(); @@ -285,6 +284,7 @@ public function discoverPeers() } */ + /* public function getValidAddresses() { $curl = curl_init(); @@ -340,6 +340,7 @@ public function getValidAddresses() } return $addresses; } + */ public function getValidatorRewards($validatorId, $_range) { From bc61ac147ad27d82de4b80d7f68fd54274aac5df Mon Sep 17 00:00:00 2001 From: = <=> Date: Fri, 18 Nov 2022 14:16:01 -0500 Subject: [PATCH 129/162] historical_performance logic update --- .../templates/.env.j2 | 2 +- .../Controllers/Api/V1/UserController.php | 48 ++++++++++++++----- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/ansible/roles/casper-association-portal-backend/templates/.env.j2 b/ansible/roles/casper-association-portal-backend/templates/.env.j2 index 2164dc7c..283ba31f 100644 --- a/ansible/roles/casper-association-portal-backend/templates/.env.j2 +++ b/ansible/roles/casper-association-portal-backend/templates/.env.j2 @@ -34,7 +34,7 @@ MAIL_USERNAME={{ lookup('ansible.builtin.env', 'CA_MEMBER_PORT_BE_MAIL_USERNAME' MAIL_PASSWORD={{ lookup('ansible.builtin.env', 'CA_MEMBER_PORT_BE_MAIL_PASSWORD') }} MAIL_ENCRYPTION={{ lookup('ansible.builtin.env', 'CA_MEMBER_PORT_BE_MAIL_ENCRYPTION') }} MAIL_FROM_ADDRESS={{ lookup('ansible.builtin.env', 'CA_MEMBER_PORT_BE_MAIL_FROM_ADDRESS') }} -MAIL_FROM_NAME="{{ lookup('ansible.builtin.env', 'CA_MEMBER_PORT_BE_MAIL_FROM_NAME') }}" +MAIL_FROM_NAME="{{ lookup('ansible.builtin.env', 'CA_MEMBER_PORT_BE_APP_NAME') }}" AWS_ACCESS_KEY_ID={{ lookup('ansible.builtin.env', 'CA_MEMBER_PORT_BE_AWS_KEY') }} AWS_SECRET_ACCESS_KEY={{ lookup('ansible.builtin.env', 'CA_MEMBER_PORT_BE_AWS_SECRET') }} diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index c3fa6b48..d4344c3e 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -153,6 +153,14 @@ public function getUserDashboard() { "); $return["total_members"] = $total_members ? count($total_members) : 0; + // fetch MBS + $mbs = DB::select(" + SELECT mbs + FROM mbs + WHERE era_id = $current_era_id + "); + $mbs = $mbs[0]->mbs ?? 0; + // find rank $ranking = DB::select(" SELECT @@ -280,6 +288,7 @@ public function getUserDashboard() { SELECT in_current_era FROM all_node_data2 WHERE public_key = '$p' + AND bid_total_staked_amount > $mbs ORDER BY era_id DESC LIMIT $uptime_calc_size "); @@ -360,6 +369,14 @@ public function getMembershipPage() { $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) ($settings['uptime_calc_size']) : 1; + // fetch MBS + $mbs = DB::select(" + SELECT mbs + FROM mbs + WHERE era_id = $current_era_id + "); + $mbs = $mbs[0]->mbs ?? 0; + foreach ($addresses as $address) { $p = $address->public_key ?? ''; @@ -406,6 +423,7 @@ public function getMembershipPage() { SELECT in_current_era FROM all_node_data2 WHERE public_key = '$p' + AND bid_total_staked_amount > $mbs ORDER BY era_id DESC LIMIT $uptime_calc_size "); @@ -526,6 +544,14 @@ public function getNodesPage() { $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 1; + // fetch MBS + $mbs = DB::select(" + SELECT mbs + FROM mbs + WHERE era_id = $current_era_id + "); + $mbs = $mbs[0]->mbs ?? 0; + // for each member's node address foreach ($addresses as $address) { $p = $address->public_key ?? ''; @@ -567,6 +593,7 @@ public function getNodesPage() { SELECT in_current_era FROM all_node_data2 WHERE public_key = '$p' + AND bid_total_staked_amount > $mbs ORDER BY era_id DESC LIMIT $uptime_calc_size "); @@ -623,17 +650,7 @@ public function getNodesPage() { ]; } - // get mbs - $temp = DB::select(" - SELECT mbs - FROM mbs - ORDER BY era_id DESC - LIMIT 1 - "); - if (!$temp) $temp = []; - - $return['mbs'] = 0; - if (isset($temp[0])) $return['mbs'] = (int) ($temp[0]->mbs ?? 0); + $return['mbs'] = $mbs; return $this->successResponse($return); } @@ -670,6 +687,14 @@ public function getMyEras() { $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 1; + // fetch MBS + $mbs = DB::select(" + SELECT mbs + FROM mbs + WHERE era_id = $current_era_id + "); + $mbs = $mbs[0]->mbs ?? 0; + // for each member's node address foreach ($addresses as $address) { $p = $address->public_key ?? ''; @@ -711,6 +736,7 @@ public function getMyEras() { SELECT in_current_era FROM all_node_data2 WHERE public_key = '$p' + AND bid_total_staked_amount > $mbs ORDER BY era_id DESC LIMIT $uptime_calc_size "); From 8c8039c4c45cab93e9e942442087f403f7afdc3f Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Fri, 18 Nov 2022 15:51:29 -0500 Subject: [PATCH 130/162] # Historical Performance --- app/Console/Helper.php | 14 ++ .../Controllers/Api/V1/UserController.php | 152 ++++-------------- 2 files changed, 42 insertions(+), 124 deletions(-) diff --git a/app/Console/Helper.php b/app/Console/Helper.php index 54fff78d..efcb34fd 100644 --- a/app/Console/Helper.php +++ b/app/Console/Helper.php @@ -66,12 +66,26 @@ public static function getSettings() { public static function calculateUptime($baseObject, $public_address_node, $settings = null) { if (!$settings) $settings = self::getSettings(); + $current_era_id = (int) ($settings['current_era_id'] ?? 0); $uptime_calc_size = (int) ($settings['uptime_calc_size'] ?? 1); + $mbs = 0; + if (isset($baseObject->mbs)) { + $mbs = (float) ($baseObject->mbs ?? 0); + } else { + $temp = DB::select(" + SELECT mbs + FROM mbs + WHERE era_id = $current_era_id + "); + $mbs = (float) ($temp[0]->mbs ?? 0); + } + $temp = DB::select(" SELECT in_current_era FROM all_node_data2 WHERE public_key = '$public_address_node' + AND bid_total_staked_amount > $mbs ORDER BY era_id DESC LIMIT $uptime_calc_size "); diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index d4344c3e..f78e8fad 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -97,7 +97,8 @@ public function getUserDashboard() { $user = auth()->user(); $user_id = $user->id ?? 0; - $current_era_id = Helper::getCurrentERAId(); + $settings = Helper::getSettings(); + $current_era_id = (int) ($settings['current_era_id'] ?? 0); // Define complete return object $return = [ @@ -153,14 +154,6 @@ public function getUserDashboard() { "); $return["total_members"] = $total_members ? count($total_members) : 0; - // fetch MBS - $mbs = DB::select(" - SELECT mbs - FROM mbs - WHERE era_id = $current_era_id - "); - $mbs = $mbs[0]->mbs ?? 0; - // find rank $ranking = DB::select(" SELECT @@ -234,18 +227,15 @@ public function getUserDashboard() { WHERE a.era_id = $current_era_id AND b.user_id = $user_id "); - if (!$addresses) $addresses = []; - - $settings = Helper::getSettings(); - $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; - $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 1; - // for each address belonging to a user foreach ($addresses as $address) { $p = $address->public_key ?? ''; + $baseObject = new \stdClass(); + $baseObject->uptime = (float) ($address->uptime ?? 0); + $temp = DB::select(" SELECT era_id FROM all_node_data2 @@ -282,28 +272,7 @@ public function getUserDashboard() { $return["eras_active"] = $current_era_id - $eras_active; } - // Calculate historical_performance from past $uptime_calc_size eras - $missed = 0; - $temp = DB::select(" - SELECT in_current_era - FROM all_node_data2 - WHERE public_key = '$p' - AND bid_total_staked_amount > $mbs - ORDER BY era_id DESC - LIMIT $uptime_calc_size - "); - if (!$temp) $temp = []; - - $window = count($temp); - foreach ($temp as $c) { - $in = (bool) ($c->in_current_era ?? 0); - if (!$in) { - $missed += 1; - } - } - - $uptime = (float) ($address->uptime ?? 0); - $historical_performance = round((float) ($uptime * ($window - $missed) / $window), 2); + $historical_performance = Helper::calculateUptime($baseObject, $p, $settings); if ( array_key_exists($p, $return["ranking"]) && @@ -321,7 +290,8 @@ public function getUserDashboard() { } $addresses_count = count($addresses); - $addresses_count = $addresses_count ? $addresses_count : 1; + if (!$addresses_count) $addresses_count = 1; + $return["uptime"] = round((float) ($return["uptime"] / $addresses_count), 2); unset($return["ranking"]); @@ -333,8 +303,8 @@ public function getMembershipPage() { $user = auth()->user()->load(['profile']); $user_id = $user->id ?? 0; - $current_era_id = Helper::getCurrentERAId(); $settings = Helper::getSettings(); + $current_era_id = (int) ($settings['current_era_id'] ?? 0); $return = [ "node_status" => $user->node_status ?? '', @@ -367,19 +337,12 @@ public function getMembershipPage() { "); if (!$addresses) $addresses = []; - $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) ($settings['uptime_calc_size']) : 1; - - // fetch MBS - $mbs = DB::select(" - SELECT mbs - FROM mbs - WHERE era_id = $current_era_id - "); - $mbs = $mbs[0]->mbs ?? 0; - foreach ($addresses as $address) { $p = $address->public_key ?? ''; + $baseObject = new \stdClass(); + $baseObject->uptime = (float) ($address->uptime ?? 0); + $temp = DB::select(" SELECT era_id FROM all_node_data2 @@ -417,29 +380,7 @@ public function getMembershipPage() { $return["total_eras"] = $current_era_id - $total_eras; } - // Calculate historical_performance from past $uptime_calc_size eras - $missed = 0; - $temp = DB::select(" - SELECT in_current_era - FROM all_node_data2 - WHERE public_key = '$p' - AND bid_total_staked_amount > $mbs - ORDER BY era_id DESC - LIMIT $uptime_calc_size - "); - if (!$temp) $temp = []; - - $window = count($temp); - - foreach ($temp as $c) { - $in = (bool) ($c->in_current_era ?? 0); - if (!$in) { - $missed += 1; - } - } - - $uptime = (float) ($address->uptime ?? 0); - $historical_performance = round((float) ($uptime * ($window - $missed) / $window), 2); + $historical_performance = Helper::calculateUptime($baseObject, $p, $settings); $return["total_bad_marks"] += $total_bad_marks; $return["peers"] += (int) ($address->peers ?? 0); @@ -459,8 +400,9 @@ public function getNodesPage() { $user_id = $user->id ?? 0; $nodeHelper = new NodeHelper(); - $current_era_id = Helper::getCurrentERAId(); + $settings = Helper::getSettings(); + $current_era_id = (int) ($settings['current_era_id'] ?? 0); // Define complete return object $return = [ @@ -542,8 +484,6 @@ public function getNodesPage() { "); if (!$addresses) $addresses = []; - $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 1; - // fetch MBS $mbs = DB::select(" SELECT mbs @@ -556,6 +496,10 @@ public function getNodesPage() { foreach ($addresses as $address) { $p = $address->public_key ?? ''; + $baseObject = new \stdClass(); + $baseObject->uptime = (float) ($address->uptime ?? 0); + $baseObject->mbs = $mbs; + $temp = DB::select(" SELECT era_id FROM all_node_data2 @@ -587,29 +531,7 @@ public function getNodesPage() { if (isset($temp[0])) $total_eras = (int) ($temp[0]->era_id ?? 0); if ($current_era_id > $total_eras) $total_eras = $current_era_id - $total_eras; - // Calculate historical_performance from past $uptime_calc_size eras - $missed = 0; - $temp = DB::select(" - SELECT in_current_era - FROM all_node_data2 - WHERE public_key = '$p' - AND bid_total_staked_amount > $mbs - ORDER BY era_id DESC - LIMIT $uptime_calc_size - "); - if (!$temp) $temp = []; - - $window = count($temp); - - foreach ($temp as $c) { - $in = (bool) ($c->in_current_era ?? 0); - if (!$in) { - $missed += 1; - } - } - - $uptime = (float) ($address->uptime ?? 0); - $historical_performance = round((float) ($uptime * ($window - $missed) / $window), 2); + $historical_performance = Helper::calculateUptime($baseObject, $p, $settings); $one_day_ago = Carbon::now('UTC')->subHours(24); $temp = DB::select(" @@ -659,8 +581,8 @@ public function getMyEras() { $user = auth()->user(); $user_id = $user->id ?? 0; - $current_era_id = Helper::getCurrentERAId(); $settings = Helper::getSettings(); + $current_era_id = (int) ($settings['current_era_id'] ?? 0); // define return object $return = [ @@ -699,6 +621,10 @@ public function getMyEras() { foreach ($addresses as $address) { $p = $address->public_key ?? ''; + $baseObject = new \stdClass(); + $baseObject->uptime = (float) ($address->uptime ?? 0); + $baseObject->mbs = $mbs; + $temp = DB::select(" SELECT era_id FROM all_node_data2 @@ -729,31 +655,9 @@ public function getMyEras() { $eras_active = 0; if (isset($temp[0])) $eras_active = (int) ($temp[0]->era_id ?? 0); if ($eras_active < $current_era_id) $eras_active = $current_era_id - $eras_active; - - // Calculate historical_performance from past $uptime_calc_size eras - $missed = 0; - $temp = DB::select(" - SELECT in_current_era - FROM all_node_data2 - WHERE public_key = '$p' - AND bid_total_staked_amount > $mbs - ORDER BY era_id DESC - LIMIT $uptime_calc_size - "); - if (!$temp) $temp = []; - - $window = count($temp); - - foreach ($temp as $c) { - $in = (bool) ($c->in_current_era ?? 0); - if (!$in) { - $missed += 1; - } - } - - $uptime = (float) ($address->uptime ?? 0); - $historical_performance = round((float) ($uptime * ($window - $missed) / $window), 2); - + + $historical_performance = Helper::calculateUptime($baseObject, $p, $settings); + $return["addresses"][$p] = [ "uptime" => round($historical_performance, 2), "eras_active" => $eras_active, From 056f91e1ab1c916e78bae18d013606ea3d2f1f40 Mon Sep 17 00:00:00 2001 From: Ledgerleap Date: Mon, 21 Nov 2022 13:35:38 -0500 Subject: [PATCH 131/162] # Horizon --- README.md | 10 + app/Console/Commands/CheckBallot2.php | 37 + app/Console/Commands/CheckNodeStatus.php | 2 +- app/Console/Commands/PerkCheck.php | 3 + app/Console/Helper.php | 160 ++- app/Console/Kernel.php | 3 + .../Controllers/Api/V1/AdminController.php | 477 +++------ .../Controllers/Api/V1/AuthController.php | 1 - .../Controllers/Api/V1/InstallController.php | 17 +- .../Controllers/Api/V1/PerkController.php | 23 +- .../Controllers/Api/V1/UserController.php | 974 +++--------------- app/Http/EmailerHelper.php | 81 +- app/Jobs/BallotNotification.php | 82 ++ app/Jobs/BallotReminder24.php | 100 ++ app/Jobs/PerkNotification.php | 48 + app/Providers/HorizonServiceProvider.php | 42 + composer.json | 6 +- config/app.php | 1 + config/horizon.php | 230 +++++ config/queue.php | 10 +- .../2022_11_20_165842_create_jobs_table.php | 36 + ...022_11_21_022031_udpate_table_ballot_4.php | 15 + .../2022_11_21_165620_update_mbs.php | 19 + public/vendor/horizon/app-dark.css | 8 + public/vendor/horizon/app.css | 8 + public/vendor/horizon/app.js | 2 + public/vendor/horizon/img/favicon.png | Bin 0 -> 648 bytes public/vendor/horizon/img/horizon.svg | 4 + public/vendor/horizon/img/sprite.svg | 806 +++++++++++++++ public/vendor/horizon/mix-manifest.json | 8 + resources/lang/en/api.php | 2 +- routes/api.php | 2 +- 32 files changed, 2026 insertions(+), 1191 deletions(-) create mode 100644 app/Console/Commands/CheckBallot2.php create mode 100644 app/Jobs/BallotNotification.php create mode 100644 app/Jobs/BallotReminder24.php create mode 100644 app/Jobs/PerkNotification.php create mode 100644 app/Providers/HorizonServiceProvider.php create mode 100644 config/horizon.php create mode 100644 database/migrations/2022_11_20_165842_create_jobs_table.php create mode 100644 database/migrations/2022_11_21_022031_udpate_table_ballot_4.php create mode 100644 database/migrations/2022_11_21_165620_update_mbs.php create mode 100644 public/vendor/horizon/app-dark.css create mode 100644 public/vendor/horizon/app.css create mode 100644 public/vendor/horizon/app.js create mode 100644 public/vendor/horizon/img/favicon.png create mode 100644 public/vendor/horizon/img/horizon.svg create mode 100644 public/vendor/horizon/img/sprite.svg create mode 100644 public/vendor/horizon/mix-manifest.json diff --git a/README.md b/README.md index 3cd593f4..8137ea3b 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,16 @@ curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/loca # Load php_gmp extension echo "extension=php_gmp.so" | sudo tee /etc/php/7.4/mods-available/ext_gmp.ini sudo ln -s /etc/php/7.4/mods-available/ext_gmp.ini /etc/php/7.4/cli/conf.d/20-ext_gmp.ini + +# Configure Horizon +php artisan queue:table +php artisan migrate +## Install Redis. Link: https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-redis-on-ubuntu-20-04 +composer require predis/predis +composer require laravel/horizon +php artisan horizon:install +php artisan horizon:publish +## Configure Supervisor. Link: https://laravel.com/docs/9.x/horizon#installing-supervisor ( Only 'Installing Supervisor', 'Supervisor Configuration', 'Starting Supervisor' Sections ) ``` Setup the repo according to our VHOST path. Note, the actual VHOST path in this case should be set to **/var/www/CasperAssociationPortalBackend/public** diff --git a/app/Console/Commands/CheckBallot2.php b/app/Console/Commands/CheckBallot2.php new file mode 100644 index 00000000..94d207e6 --- /dev/null +++ b/app/Console/Commands/CheckBallot2.php @@ -0,0 +1,37 @@ +where('status', 'active') + ->where('time_end', '<=', Carbon::now('UTC')->addHours(24)) + ->where('time_end', '>', Carbon::now('UTC')) + ->where('reminder_24_sent', false) + ->orderBy('time_end', 'asc') + ->get(); + + if ($ballots && count($ballots) > 0) { + foreach ($ballots as $ballot) { + BallotReminder24::dispatch($ballot)->onQueue('default_long'); + } + } + + return 0; + } +} \ No newline at end of file diff --git a/app/Console/Commands/CheckNodeStatus.php b/app/Console/Commands/CheckNodeStatus.php index 57971602..582fd4ce 100644 --- a/app/Console/Commands/CheckNodeStatus.php +++ b/app/Console/Commands/CheckNodeStatus.php @@ -93,7 +93,7 @@ public function handle() { // Check Redmarks if ($redmarks_revoke > 0) { - $bad_marks = Helper::calculateBadMarks($temp, $public_address_node, $settings); + $bad_marks = Helper::calculateBadMarksRevoke($temp, $public_address_node, $settings); if ($bad_marks > $redmarks_revoke) { $address->extra_status = 'Suspended'; diff --git a/app/Console/Commands/PerkCheck.php b/app/Console/Commands/PerkCheck.php index 5f955fe6..62571af5 100644 --- a/app/Console/Commands/PerkCheck.php +++ b/app/Console/Commands/PerkCheck.php @@ -3,6 +3,7 @@ namespace App\Console\Commands; use App\Models\Perk; +use App\Jobs\PerkNotification; use Carbon\Carbon; use Illuminate\Console\Command; @@ -51,6 +52,8 @@ public function handle() $perk->status = 'active'; $perk->visibility = 'visible'; $perk->save(); + + PerkNotification::dispatch($perk)->onQueue('default_long'); } // check perk expired diff --git a/app/Console/Helper.php b/app/Console/Helper.php index efcb34fd..9ec56e2e 100644 --- a/app/Console/Helper.php +++ b/app/Console/Helper.php @@ -63,6 +63,25 @@ public static function getSettings() { return $settings; } + public function getActiveMembers() { + $current_era_id = Helper::getCurrentERAId(); + $temp = DB::select(" + SELECT + a.public_key, + c.id, c.email, c.pseudonym, c.node_status, + d.status, d.extra_status + FROM all_node_data2 AS a + JOIN user_addresses AS b + ON a.public_key = b.public_address_node + JOIN users AS c + ON b.user_id = c.id + JOIN profile AS d + ON c.id = d.user_id + WHERE a.era_id = $current_era_id and c.email is not NULL and d.status = 'approved' + "); + return ($temp ?? []); + } + public static function calculateUptime($baseObject, $public_address_node, $settings = null) { if (!$settings) $settings = self::getSettings(); @@ -108,7 +127,66 @@ public static function calculateUptime($baseObject, $public_address_node, $setti return round($uptime, 2); } - public static function calculateBadMarks($baseObject, $public_address_node, $settings = null) { + public static function calculateVariables($identifier, $public_address_node, $settings = null) { + if (!$settings) $settings = self::getSettings(); + + $current_era_id = (int) ($settings['current_era_id'] ?? 0); + + if ($identifier == 'good_standing_eras') { + $temp = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$public_address_node' + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) + ORDER BY era_id DESC + LIMIT 1 + "); + $value = $current_era_id - (int) ($temp[0]->era_id ?? 0); + if ($value > 0) return $value; + return 0; + } else if ($identifier == 'total_active_eras') { + $temp = DB::select(" + SELECT count(id) as tCount + FROM all_node_data2 + WHERE public_key = '$public_address_node' + AND in_current_era = 1 + AND bid_inactive = 0 + "); + return (int) ($temp[0]->tCount ?? 0); + } else if ($identifier == 'bad_marks_info') { + $temp = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$public_address_node' + AND ( + in_current_era = 0 OR + bid_inactive = 1 + ) + ORDER BY era_id DESC + "); + $total_bad_marks = count($temp ?? []); + $eras_since_bad_mark = $current_era_id - (int) ($temp[0]->era_id ?? 0); + return [ + 'total_bad_marks' => $total_bad_marks, + 'eras_since_bad_mark' => $eras_since_bad_mark + ]; + } else if ($identifier == 'min_era') { + $temp = DB::select(" + SELECT era_id + FROM all_node_data2 + WHERE public_key = '$public_address_node' + ORDER BY era_id ASC + LIMIT 1 + "); + return (int) ($temp[0]->era_id ?? 0); + } + return 0; + } + + public static function calculateBadMarksRevoke($baseObject, $public_address_node, $settings = null) { if (!$settings) $settings = self::getSettings(); $current_era_id = (int) ($settings['current_era_id'] ?? 0); @@ -146,6 +224,77 @@ public static function getCurrentERAId() { return 0; } + public static function getTotalScore($r, $max_delegators, $max_stake_amount) { + $uptime_rate = $fee_rate = $count_rate = $stake_rate = 25; + if (isset($r->uptime_rate)) $uptime_rate = (float) $r->uptime_rate; + if (isset($r->fee_rate)) $fee_rate = (float) $r->fee_rate; + if (isset($r->count_rate)) $count_rate = (float) $r->count_rate; + if (isset($r->stake_rate)) $stake_rate = (float) $r->stake_rate; + + $uptime_score = (float) ($uptime_rate * (float) $r->uptime / 100); + $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; + + $fee_score = $fee_rate * (1 - (float) ((float) $r->bid_delegation_rate / 100)); + $fee_score = $fee_score < 0 ? 0 : $fee_score; + + $count_score = (float) ((float) $r->bid_delegators_count / $max_delegators) * $count_rate; + $count_score = $count_score < 0 ? 0 : $count_score; + + $stake_score = (float) ((float) $r->bid_total_staked_amount / $max_stake_amount) * $stake_rate; + $stake_score = $stake_score < 0 ? 0 : $stake_score; + + return $uptime_score + $fee_score + $count_score + $stake_score; + } + + public static function getRanking($current_era_id) { + $rankingData = []; + + $ranking = DB::select(" + SELECT + public_key, uptime, + bid_delegators_count, + bid_delegation_rate, + bid_total_staked_amount + FROM all_node_data2 + WHERE era_id = $current_era_id + AND in_current_era = 1 + AND in_next_era = 1 + AND in_auction = 1 + "); + $max_delegators = $max_stake_amount = 0; + + foreach ($ranking as $r) { + if ((int) $r->bid_delegators_count > $max_delegators) { + $max_delegators = (int) $r->bid_delegators_count; + } + if ((int) $r->bid_total_staked_amount > $max_stake_amount) { + $max_stake_amount = (int) $r->bid_total_staked_amount; + } + } + + foreach ($ranking as $r) { + $rankingData['ranking'][$r->public_key] = self::getTotalScore($r, $max_delegators, $max_stake_amount); + } + + uasort($rankingData['ranking'], function($x, $y) { + if ($x == $y) { + return 0; + } + return ($x > $y) ? -1 : 1; + }); + + $sorted_ranking = []; + $i = 1; + foreach ($rankingData['ranking'] as $public_key => $score) { + $sorted_ranking[$public_key] = $i; + $i += 1; + } + $rankingData['ranking'] = $sorted_ranking; + $rankingData['node_rank_total'] = count($sorted_ranking); + + return $rankingData; + } + public static function isAccessBlocked($user, $page) { if ($user->role == 'admin') return false; $flag = false; @@ -160,8 +309,7 @@ public static function isAccessBlocked($user, $page) { return $flag; } - public static function publicKeyToAccountHash($public_key) - { + public static function publicKeyToAccountHash($public_key) { $public_key = (string)$public_key; $first_byte = substr($public_key, 0, 2); @@ -179,8 +327,7 @@ public static function publicKeyToAccountHash($public_key) return $account_hash; } - public static function getAccountInfoStandard($user) - { + public static function getAccountInfoStandard($user) { $vid = strtolower($user->public_address_node ?? ''); if (!$vid) return; @@ -400,8 +547,7 @@ public static function getNodeInfo($user, $public_address_node = null) } */ - public static function paginate($items, $perPage = 5, $page = null, $options = []) - { + public static function paginate($items, $perPage = 5, $page = null, $options = []) { $page = $page ?: (Paginator::resolveCurrentPage() ?: 1); $items = $items instanceof Collection ? $items : Collection::make($items); return new LengthAwarePaginator($items->forPage($page, $perPage), $items->count(), $perPage, $page, $options); diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 521159f7..9682bc29 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -35,6 +35,9 @@ protected function schedule(Schedule $schedule) $schedule->command('ballot:check') ->everyMinute() ->runInBackground(); + $schedule->command('ballot:check2') + ->hourly() + ->runInBackground(); $schedule->command('perk:check') ->everyThirtyMinutes() ->runInBackground(); diff --git a/app/Http/Controllers/Api/V1/AdminController.php b/app/Http/Controllers/Api/V1/AdminController.php index f77b8d32..1fa18270 100644 --- a/app/Http/Controllers/Api/V1/AdminController.php +++ b/app/Http/Controllers/Api/V1/AdminController.php @@ -7,6 +7,8 @@ use App\Http\Controllers\Controller; use App\Http\EmailerHelper; +use App\Jobs\BallotNotification; + use App\Mail\AdminAlert; use App\Mail\ResetKYC; use App\Mail\ResetPasswordMail; @@ -75,16 +77,14 @@ public function allErasUser($id) { ); } - $current_era_id = Helper::getCurrentERAId(); $settings = Helper::getSettings(); + $current_era_id = (int) ($settings['current_era_id'] ?? 0); $return = [ - "column_count" => 2, - "eras" => [], - "addresses" => [] + 'eras' => [], + 'addresses' => [] ]; - $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 1; $era_minus_360 = $current_era_id - $uptime_calc_size; @@ -135,40 +135,30 @@ public function allErasUser($id) { if (!isset($sorted_eras[$era_id])) { $sorted_eras[$era_id] = [ - "era_start_time" => $era_start_time, - "addresses" => [] + 'era_start_time' => $era_start_time, + 'addresses' => [] ]; } - $sorted_eras[$era_id]["addresses"][$public_key] = [ - "in_pool" => $era->in_auction, - "rewards" => $era->uptime, + $sorted_eras[$era_id]['addresses'][$public_key] = [ + 'in_pool' => $era->in_auction, + 'rewards' => $era->uptime, ]; } - $return["eras"] = $sorted_eras; - $column_count = 0; - - foreach ($return["eras"] as $era) { - $count = $era["addresses"] ? count($era["addresses"]) : 0; - if ($count > $column_count) { - $column_count = $count; - } - } - - $return["column_count"] = $column_count + 1; + $return['eras'] = $sorted_eras; return $this->successResponse($return); } public function allEras() { - $current_era_id = Helper::getCurrentERAId(); $settings = Helper::getSettings(); + $current_era_id = (int) ($settings['current_era_id'] ?? 0); // define return object $return = [ - "addresses" => [], - "users" => [] + 'addresses' => [], + 'users' => [] ]; // get addresses data @@ -182,72 +172,24 @@ public function allEras() { "); if (!$addresses) $addresses = []; - $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; - $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 1; - // for each member's node address foreach ($addresses as $address) { $p = $address->public_key ?? ''; - $temp = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - ORDER BY era_id DESC - "); - if (!$temp) $temp = []; - - $eras_since_bad_mark = $current_era_id; - if (isset($temp[0])) $eras_since_bad_mark = $current_era_id - (int) ($temp[0]->era_id ?? 0); - $total_bad_marks = count($temp); - - $temp = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - ORDER BY era_id ASC - LIMIT 1 - "); - if (!$temp) $temp = []; - - $eras_active = 0; - if (isset($temp[0])) $eras_active = (int) ($temp[0]->era_id ?? 0); - if ($eras_active < $current_era_id) $eras_active = $current_era_id - $eras_active; - - // Calculate historical_performance from past $uptime_calc_size eras - $missed = 0; - $temp = DB::select(" - SELECT in_current_era - FROM all_node_data2 - WHERE public_key = '$p' - ORDER BY era_id DESC - LIMIT $uptime_calc_size - "); - if (!$temp) $temp = []; - - $window = count($temp); - - foreach ($temp as $c) { - $in = (bool) ($c->in_current_era ?? 0); - if (!$in) { - $missed += 1; - } - } + $badMarkValues = Helper::calculateVariables('bad_marks_info', $p, $settings); + $total_bad_marks = (int) ($badMarkValues['total_bad_marks'] ?? 0); + $eras_since_bad_mark = (int) ($badMarkValues['eras_since_bad_mark'] ?? $current_era_id); + + $min_era_id = Helper::calculateVariables('min_era', $p, $settings); + $eras_active = $current_era_id - $min_era_id; - $uptime = (float) ($address->uptime ?? 0); - $value = $uptime; - if ($window > 0) $value = (float) ($uptime * ($window - $missed) / $window); - $historical_performance = round($value, 2); + $historical_performance = Helper::calculateUptime($address, $p, $settings); - $return["addresses"][$p] = [ - "uptime" => $historical_performance, - "eras_active" => $eras_active, - "eras_since_bad_mark" => $eras_since_bad_mark, - "total_bad_marks" => $total_bad_marks + $return['addresses'][$p] = [ + 'uptime' => $historical_performance, + 'eras_active' => $eras_active, + 'eras_since_bad_mark' => $eras_since_bad_mark, + 'total_bad_marks' => $total_bad_marks ]; } @@ -262,10 +204,10 @@ public function allEras() { if (!$users) $users = []; foreach ($users as $user) { - $return["users"][] = [ - "user_id" => $user->id, - "name" => $user->first_name . ' ' . $user->last_name, - "pseudonym" => $user->pseudonym, + $return['users'][] = [ + 'user_id' => $user->id, + 'name' => $user->first_name . ' ' . $user->last_name, + 'pseudonym' => $user->pseudonym, ]; } @@ -273,75 +215,20 @@ public function allEras() { } public function getNodesPage() { - $current_era_id = Helper::getCurrentERAId(); + $settings = Helper::getSettings(); + $current_era_id = (int) ($settings['current_era_id'] ?? 0); - // Define complete return object $return = [ - "mbs" => 0, - "ranking" => [], - "addresses" => [] + 'mbs' => 0, + 'ranking' => [], + 'addresses' => [] ]; $nodeHelper = new NodeHelper(); - // get ranking - $ranking = DB::select(" - SELECT - public_key, - uptime, - bid_delegators_count, - bid_delegation_rate, - bid_total_staked_amount - FROM all_node_data2 - WHERE era_id = $current_era_id - AND in_current_era = 1 - AND in_next_era = 1 - AND in_auction = 1 - "); - if (!$ranking) $ranking = []; + $rankingData = Helper::getRanking($current_era_id); - $max_delegators = 0; - $max_stake_amount = 0; - foreach ($ranking as $r) { - if ((int) $r->bid_delegators_count > $max_delegators) { - $max_delegators = (int) $r->bid_delegators_count; - } - if ((int) $r->bid_total_staked_amount > $max_stake_amount) { - $max_stake_amount = (int) $r->bid_total_staked_amount; - } - } - - foreach ($ranking as $r) { - $uptime_score = (float) (25 * (float) $r->uptime / 100); - $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; - - $fee_score = 25 * (1 - (float) ($r->bid_delegation_rate / 100)); - $fee_score = $fee_score < 0 ? 0 : $fee_score; - - $count_score = (float) ($r->bid_delegators_count / $max_delegators) * 25; - $count_score = $count_score < 0 ? 0 : $count_score; - - $stake_score = (float) ($r->bid_total_staked_amount / $max_stake_amount) * 25; - $stake_score = $stake_score < 0 ? 0 : $stake_score; - - $return["ranking"][$r->public_key] = $uptime_score + $fee_score + $count_score + $stake_score; - } - - uasort($return["ranking"], function($x, $y) { - if ($x == $y) { - return 0; - } - return ($x > $y) ? -1 : 1; - }); - - $sorted_ranking = []; - $i = 1; - foreach ($return["ranking"] as $public_key => $score) { - $sorted_ranking[$public_key] = $i; - $i += 1; - } - - $return["ranking"] = $sorted_ranking; + $return['ranking'] = $rankingData['ranking']; // get user addresses $addresses = DB::select(" @@ -354,8 +241,8 @@ public function getNodesPage() { JOIN user_addresses AS b ON a.public_key = b.public_address_node JOIN users AS c - ON b.user_id = c.id - WHERE a.era_id = $current_era_id + ON b.user_id = c.id + WHERE a.era_id = $current_era_id "); if (!$addresses) $addresses = []; @@ -363,34 +250,14 @@ public function getNodesPage() { foreach ($addresses as $address) { $a = $address->public_key ?? ''; - $temp = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$a' - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - ORDER BY era_id DESC - "); - if (!$temp) $temp = []; - - $eras_since_bad_mark = $current_era_id; - if (isset($temp[0])) $eras_since_bad_mark = $current_era_id - (int) ($temp[0]->era_id ?? 0); - $total_bad_marks = count($temp); + $badMarkValues = Helper::calculateVariables('bad_marks_info', $a, $settings); + $total_bad_marks = (int) ($badMarkValues['total_bad_marks'] ?? 0); + $eras_since_bad_mark = (int) ($badMarkValues['eras_since_bad_mark'] ?? $current_era_id); - $temp = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$a' - ORDER BY era_id ASC - LIMIT 1 - "); - if (!$temp) $temp = []; + $min_era_id = Helper::calculateVariables('min_era', $a, $settings); + $total_eras = $current_era_id - $min_era_id; - $total_eras = 0; - if (isset($temp[0])) $total_eras = (int) ($temp[0]->era_id ?? 0); - if ($current_era_id > $total_eras) $total_eras = $current_era_id - $total_eras; + $historical_performance = Helper::calculateUptime($address, $a, $settings); // Calc earning $one_day_ago = Carbon::now('UTC')->subHours(24); @@ -404,8 +271,7 @@ public function getNodesPage() { "); if (!$temp) $temp = []; - $daily_earning = 0; - if (isset($temp[0])) $daily_earning = (float) ($temp[0]->bid_self_staked_amount ?? 0); + $daily_earning = (float) ($temp[0]->bid_self_staked_amount ?? 0); $daily_earning = (float) $address->bid_self_staked_amount - $daily_earning; $daily_earning = $daily_earning < 0 ? 0 : $daily_earning; @@ -423,22 +289,22 @@ public function getNodesPage() { $failing = 0; } - $return["addresses"][$a] = [ - "stake_amount" => $address->bid_total_staked_amount, - "delegators" => $address->bid_delegators_count, - "uptime" => $address->uptime, - "update_responsiveness" => 100, - "peers" => $address->peers, - "daily_earning" => $daily_earning, - "total_eras" => $total_eras, - "eras_since_bad_mark" => $eras_since_bad_mark, - "total_bad_marks" => $total_bad_marks, - "failing" => $failing, - "validator_rewards" => [ - "day" => $earning_day, - "week" => $earning_week, - "month" => $earning_month, - "year" => $earning_year + $return['addresses'][$a] = [ + 'stake_amount' => $address->bid_total_staked_amount, + 'delegators' => $address->bid_delegators_count, + 'uptime' => $historical_performance, + 'update_responsiveness' => 100, + 'peers' => $address->peers, + 'daily_earning' => $daily_earning, + 'total_eras' => $total_eras, + 'eras_since_bad_mark' => $eras_since_bad_mark, + 'total_bad_marks' => $total_bad_marks, + 'failing' => $failing, + 'validator_rewards' => [ + 'day' => $earning_day, + 'week' => $earning_week, + 'month' => $earning_month, + 'year' => $earning_year ] ]; } @@ -450,10 +316,7 @@ public function getNodesPage() { ORDER BY era_id DESC LIMIT 1 "); - if (!$temp) $temp = []; - - $return['mbs'] = 0; - if (isset($temp[0])) $return['mbs'] = (int) ($temp[0]->mbs ?? 0); + $return['mbs'] = (int) ($temp[0]->mbs ?? 0); return $this->successResponse($return); } @@ -735,8 +598,7 @@ public function getUsers(Request $request) WHERE b.user_id = $userId and a.era_id = $current_era_id "); - $self_staked_amount = 0; - if ($temp && count($temp) > 0) $self_staked_amount = (float) $temp[0]->self_staked_amount; + $self_staked_amount = (float) ($temp[0]->self_staked_amount ?? 0); $user->self_staked_amount = round($self_staked_amount, 2); } } @@ -747,14 +609,9 @@ public function getUsers(Request $request) public function getUserDetail($id) { $user = User::where('id', $id)->first(); - if (!$user || $user->role == 'admin') { - return $this->errorResponse( - __('api.error.not_found'), - Response::HTTP_NOT_FOUND - ); + return $this->errorResponse(__('api.error.not_found'), Response::HTTP_NOT_FOUND); } - $user = $user->load(['pagePermissions', 'profile', 'shuftipro', 'shuftiproTemp']); $status = 'Not Verified'; @@ -772,10 +629,11 @@ public function getUserDetail($id) } $user->membership_status = $status; - // $user->metric = Helper::getNodeInfo($user); - + $addresses = $user->addresses ?? []; - $current_era_id = Helper::getCurrentERAId(); + + $settings = Helper::getSettings(); + $current_era_id = (int) ($settings['current_era_id'] ?? 0); foreach ($addresses as &$addressItem) { $temp = AllNodeData2::select([ @@ -798,35 +656,19 @@ public function getUserDetail($id) $addressItem->update_responsiveness = 100; $p = $addressItem->public_address_node; - $total_bad_marks = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - ORDER BY era_id DESC - "); - $eras_active = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - ORDER BY era_id ASC - LIMIT 1 - "); - $eras_active = (int)($eras_active[0]->era_id ?? 0); + $historical_performance = Helper::calculateUptime($addressItem, $p, $settings); + $addressItem->uptime = $historical_performance; - $eras_since_bad_mark = $total_bad_marks[0] ?? array(); - $eras_since_bad_mark = $current_era_id - (int) ($eras_since_bad_mark->era_id ?? 0); - $total_bad_marks = count((array)$total_bad_marks); + $badMarkValues = Helper::calculateVariables('bad_marks_info', $p, $settings); + $total_bad_marks = (int) ($badMarkValues['total_bad_marks'] ?? 0); + $eras_since_bad_mark = (int) ($badMarkValues['eras_since_bad_mark'] ?? $current_era_id); + + $min_era_id = Helper::calculateVariables('min_era', $p, $settings); + $eras_active = $current_era_id - $min_era_id; $addressItem->eras_since_bad_mark = $eras_since_bad_mark; - $addressItem->total_bad_marks = $total_bad_marks; - $addressItem->eras_active = 0; - if ($current_era_id > $eras_active) { - $addressItem->eras_active = $current_era_id - $eras_active; - } + $addressItem->total_bad_marks = $total_bad_marks; + $addressItem->eras_active = $eras_active; } } @@ -839,77 +681,65 @@ public function infoDashboard(Request $request) { $current_era_id = Helper::getCurrentERAId(); - // define return object - $return = array( - "total_users" => 0, - "total_stake" => 0, - "total_delegators" => 0, - "avg_uptime" => 0, - "avg_responsiveness" => 0, - "peers" => 0, - "new_users_ready" => 0, - "id_to_review" => 0, - "failing_nodes" => 0, - "perks_active" => 0, - "perks_views" => 0, - "new_comments" => 0, - "new_threads" => 0 - ); + $return = [ + 'total_users' => 0, + 'total_stake' => 0, + 'total_delegators' => 0, + 'avg_uptime' => 0, + 'avg_responsiveness' => 0, + 'new_users_ready' => 0, + 'id_to_review' => 0, + 'perks_active' => 0, + 'perks_views' => 0, + 'new_comments' => 0, + 'new_threads' => 0 + ]; - // get total users - $total_users = DB::select(" - SELECT pseudonym + $temp = DB::select(" + SELECT count(pseudonym) as totalCount FROM users - WHERE role = 'member' + WHERE role = 'member' "); - $total_users = $total_users ? count($total_users) : 0; + $total_users = (int) ($temp[0]->totalCount ?? 0); - // get total member stake - $total_stake = DB::select(" + $temp = DB::select(" SELECT - SUM(a.bid_total_staked_amount) + SUM(a.bid_total_staked_amount) as totalSum FROM all_node_data2 AS a LEFT JOIN user_addresses AS b ON a.public_key = b.public_address_node WHERE a.era_id = $current_era_id AND b.user_id IS NOT NULL "); - $total_stake = (array)($total_stake[0] ?? array()); - $total_stake = (int)($total_stake['SUM(a.bid_total_staked_amount)'] ?? 0); + $total_stake = (int) ($temp[0]->totalSum ?? 0); - // get total delegators across all member nodes - $total_delegators = DB::select(" + $temp = DB::select(" SELECT - SUM(a.bid_delegators_count) + SUM(a.bid_delegators_count) as totalSum FROM all_node_data2 AS a LEFT JOIN user_addresses AS b ON a.public_key = b.public_address_node WHERE a.era_id = $current_era_id AND b.user_id IS NOT NULL "); - $total_delegators = (array)($total_delegators[0] ?? array()); - $total_delegators = (int)($total_delegators['SUM(a.bid_delegators_count)'] ?? 0); + $total_delegators = (int) ($temp[0]->totalSum ?? 0); - // get avg uptime of all user nodes - $uptime_nodes = DB::select(" + $temp = DB::select(" SELECT SUM(a.uptime) AS numerator, COUNT(a.uptime) AS denominator FROM all_node_data2 AS a LEFT JOIN user_addresses AS b ON a.public_key = b.public_address_node - WHERE era_id = $current_era_id + WHERE era_id = $current_era_id AND b.user_id IS NOT NULL "); - $avg_uptime = (array)($uptime_nodes[0] ?? array()); - $avg_uptime = ( - ($avg_uptime['numerator'] ?? 0) / - ($avg_uptime['denominator'] ? $avg_uptime['denominator'] : 1) - ); + $avg_uptime = (float) (($temp[0]->numerator ?? 0) / ($temp[0]->denominator ?? 1)); + $avg_uptime = round($avg_uptime, 2); - // get avg responsiveness $avg_responsiveness = 100; + /* // get max peers $max_peers = DB::select(" SELECT MAX(a.port8888_peers) AS max_peers @@ -921,9 +751,10 @@ public function infoDashboard(Request $request) "); $max_peers = (array)($max_peers[0] ?? array()); $max_peers = $max_peers['max_peers'] ?? 0; + */ // get new users ready for admin review - $new_users_ready = User::where('banned', 0) + $new_users_ready = User::where('banned', 0) ->where('role', 'member') ->where(function ($q) { $q->where('users.node_verified_at', null) @@ -932,7 +763,7 @@ public function infoDashboard(Request $request) })->count(); // get new users ready for kyc review - $id_to_review = User::where('users.role', 'member') + $id_to_review = User::where('users.role', 'member') ->where('banned', 0) ->join('profile', function ($query) { $query->on('profile.user_id', '=', 'users.id') @@ -941,8 +772,9 @@ public function infoDashboard(Request $request) ->join('shuftipro', 'shuftipro.user_id', '=', 'users.id') ->count(); + /* // get failing member nodes - $failing_nodes = DB::select(" + $failing_nodes = DB::select(" SELECT a.bid_inactive, a.in_current_era FROM all_node_data2 AS a @@ -955,40 +787,41 @@ public function infoDashboard(Request $request) a.in_current_era = 0 ) "); + */ - $timeframe_perk = $request->timeframe_perk ?? 'last_7days'; - $timeframe_comments = $request->timeframe_comments ?? 'last_7days'; + $timeframe_perk = $request->timeframe_perk ?? 'last_7days'; + $timeframe_comments = $request->timeframe_comments ?? 'last_7days'; $timeframe_discussions = $request->timeframe_discussions ?? 'last_7days'; // last_24hs, last_7days, last_30days, last_year if ($timeframe_perk == 'last_24hs') { - $timeframe_perk = Carbon::now('UTC')->subHours(24); + $timeframe_perk = Carbon::now('UTC')->subHours(24); } else if ($timeframe_perk == 'last_30days') { - $timeframe_perk = Carbon::now('UTC')->subDays(30); + $timeframe_perk = Carbon::now('UTC')->subDays(30); } else if ($timeframe_perk == 'last_year') { - $timeframe_perk = Carbon::now('UTC')->subYear(); + $timeframe_perk = Carbon::now('UTC')->subYear(); } else { - $timeframe_perk = Carbon::now('UTC')->subDays(7); + $timeframe_perk = Carbon::now('UTC')->subDays(7); } if ($timeframe_comments == 'last_24hs') { - $timeframe_comments = Carbon::now('UTC')->subHours(24); + $timeframe_comments = Carbon::now('UTC')->subHours(24); } else if ($timeframe_comments == 'last_30days') { - $timeframe_comments = Carbon::now('UTC')->subDays(30); + $timeframe_comments = Carbon::now('UTC')->subDays(30); } else if ($timeframe_comments == 'last_year') { - $timeframe_comments = Carbon::now('UTC')->subYear(); + $timeframe_comments = Carbon::now('UTC')->subYear(); } else { - $timeframe_comments = Carbon::now('UTC')->subDays(7); + $timeframe_comments = Carbon::now('UTC')->subDays(7); } if ($timeframe_discussions == 'last_24hs') { - $timeframe_discussions = Carbon::now('UTC')->subHours(24); + $timeframe_discussions = Carbon::now('UTC')->subHours(24); } else if ($timeframe_discussions == 'last_30days') { - $timeframe_discussions = Carbon::now('UTC')->subDays(30); + $timeframe_discussions = Carbon::now('UTC')->subDays(30); } else if ($timeframe_discussions == 'last_year') { - $timeframe_discussions = Carbon::now('UTC')->subYear(); + $timeframe_discussions = Carbon::now('UTC')->subYear(); } else { - $timeframe_discussions = Carbon::now('UTC')->subDays(7); + $timeframe_discussions = Carbon::now('UTC')->subDays(7); } // get active perks @@ -1002,33 +835,22 @@ public function infoDashboard(Request $request) ->sum('total_views'); // get comments - $new_comments = DiscussionComment::where( - 'created_at', - '>=', - $timeframe_comments - )->count(); + $new_comments = DiscussionComment::where('created_at', '>=', $timeframe_comments)->count(); // get discussions - $new_threads = Discussion::where( - 'created_at', - '>=', - $timeframe_discussions - )->count(); - - - $return["total_users"] = $total_users; - $return["total_stake"] = $total_stake; - $return["total_delegators"] = $total_delegators; - $return["avg_uptime"] = $avg_uptime; - $return["avg_responsiveness"] = $avg_responsiveness; - $return["peers"] = $max_peers; - $return["new_users_ready"] = $new_users_ready; - $return["id_to_review"] = $id_to_review; - $return["failing_nodes"] = $failing_nodes; - $return["perks_active"] = $perks_active; - $return["perks_views"] = $perks_views; - $return["new_comments"] = $new_comments; - $return["new_threads"] = $new_threads; + $new_threads = Discussion::where('created_at', '>=', $timeframe_discussions)->count(); + + $return['total_users'] = $total_users; + $return['total_stake'] = $total_stake; + $return['total_delegators'] = $total_delegators; + $return['avg_uptime'] = $avg_uptime; + $return['avg_responsiveness'] = $avg_responsiveness; + $return['new_users_ready'] = $new_users_ready; + $return['id_to_review'] = $id_to_review; + $return['perks_active'] = $perks_active; + $return['perks_views'] = $perks_views; + $return['new_comments'] = $new_comments; + $return['new_threads'] = $new_threads; return $this->successResponse($return); } @@ -1144,6 +966,10 @@ public function submitBallot(Request $request) $endTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $endTime, $timezone); $endTimeCarbon->setTimezone('UTC'); + if ($startTimeCarbon->gte($endTimeCarbon)) { + return $this->errorResponse('End datetime must greater than start datetime', Response::HTTP_BAD_REQUEST); + } + $ballot = new Ballot(); $ballot->user_id = $user->id; $ballot->title = $request->title; @@ -1202,6 +1028,9 @@ public function submitBallot(Request $request) } DB::commit(); + + BallotNotification::dispatch($ballot)->onQueue('default_long'); + return $this->metaSuccess(); } catch (\Exception $ex) { DB::rollBack(); @@ -1260,6 +1089,10 @@ public function editBallot($id, Request $request) $endTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $endTime, $timezone); $endTimeCarbon->setTimezone('UTC'); + if ($startTimeCarbon->gte($endTimeCarbon)) { + return $this->errorResponse('End datetime must greater than start datetime', Response::HTTP_BAD_REQUEST); + } + $ballot->time_begin = $startTimeCarbon; $ballot->time_end = $endTimeCarbon; $ballot->start_date = $request->start_date; @@ -1316,6 +1149,7 @@ public function editBallot($id, Request $request) } } DB::commit(); + return $this->metaSuccess(); } catch (\Exception $ex) { DB::rollBack(); @@ -2244,10 +2078,7 @@ public function updateLockRules(Request $request, $id) public function getGraphInfo(Request $request) { $user = Auth::user(); - $graphDataDay = - $graphDataWeek = - $graphDataMonth = - $graphDataYear = []; + $graphDataDay = $graphDataWeek = $graphDataMonth = $graphDataYear = []; $timeDay = Carbon::now('UTC')->subHours(24); $timeWeek = Carbon::now('UTC')->subDays(7); diff --git a/app/Http/Controllers/Api/V1/AuthController.php b/app/Http/Controllers/Api/V1/AuthController.php index 54b5a09f..da312669 100644 --- a/app/Http/Controllers/Api/V1/AuthController.php +++ b/app/Http/Controllers/Api/V1/AuthController.php @@ -61,7 +61,6 @@ public function __construct( public function testHash() { exit(Hash::make('ledgerleapllc')); } - public function devVerifyNode($address) { diff --git a/app/Http/Controllers/Api/V1/InstallController.php b/app/Http/Controllers/Api/V1/InstallController.php index 30ab5429..051c34ec 100644 --- a/app/Http/Controllers/Api/V1/InstallController.php +++ b/app/Http/Controllers/Api/V1/InstallController.php @@ -64,6 +64,21 @@ public function installEmailer() { 'subject' => 'User [email] has uploaded a letter of motivation', 'content' => 'Excellent work! You have completed all the required steps to access the member\'s dashboard.

Please log in to explore. You now have access to the following:
- Node and network metrics
- Discussion previews
- Viewing previous votes

To fully unlock your dashboard\'s features, you will need to verify yourself inside the portal. This will grant you the Casper Red Checkmark, proving to the network that you are a Verified Member worthy of a higher level of trust. This process is free and takes only 5 minutes of your time.

Verified Members access all membership perks more likely to be trusted by the public for staking delegation and even get access to a public profile. It\'s a very fast process to upgrade to a Verified Member. Just look for the get verified links on the dashboard.

Verified Members can do the following:
- Start and participate in member discussions
- Vote on protocol updates or changes
- Display a page to verify their status to the public
- Access member benefits and perks
- View all network and node metrics
- Easily view details earnings from staking
- Track all health metrics for their node
' ], + [ + 'title' => 'New Perk Created', + 'subject' => 'A new perk has been added to the portal', + 'content' => '"[perk]" is now available in the portal. Please log in and view this perk on the Perks tab.' + ], + [ + 'title' => 'New Vote Started', + 'subject' => 'A new vote is live in the Casper Members portal', + 'content' => 'Voting has started for "[vote]". Please log in to the Casper Membership portal and submit your vote on the Votes tab.' + ], + [ + 'title' => '24hr Vote Reminder', + 'subject' => 'We need your vote', + 'content' => 'Only 24 hours are left in the vote titled "[vote]". Please log in and submit your vote.' + ] ]; EmailerTriggerUser::where('id', '>', 0)->delete(); @@ -72,7 +87,7 @@ public function installEmailer() { foreach ($userData as $item) { $record = EmailerTriggerUser::where('title', $item['title'])->first(); if ($record) $record->delete(); - + $record = new EmailerTriggerUser; $record->title = $item['title']; $record->subject = $item['subject']; diff --git a/app/Http/Controllers/Api/V1/PerkController.php b/app/Http/Controllers/Api/V1/PerkController.php index 9fcdf742..db4a0f4a 100644 --- a/app/Http/Controllers/Api/V1/PerkController.php +++ b/app/Http/Controllers/Api/V1/PerkController.php @@ -9,6 +9,8 @@ use App\Models\Perk; use App\Models\PerkResult; +use App\Jobs\PerkNotification; + use Carbon\Carbon; use Illuminate\Http\Request; @@ -51,7 +53,7 @@ public function createPerk(Request $request) { $setting = $request->setting; - if ($startTimeCarbon->gt($endTimeCarbon)) { + if ($startTimeCarbon->gte($endTimeCarbon)) { return $this->errorResponse('End date must greater than start date', Response::HTTP_BAD_REQUEST); } @@ -116,6 +118,11 @@ public function createPerk(Request $request) { $perk->visibility = $visibility; $perk->status = $status; $perk->save(); + + if ($perk->visibility == 'visible' && $perk->status == 'active') { + PerkNotification::dispatch($perk)->onQueue('default_long'); + } + return $this->successResponse($perk); } @@ -146,6 +153,9 @@ public function updatePerk(Request $request, $id) return $this->errorResponse('Not found perk', Response::HTTP_BAD_REQUEST); } + $originalStatus = $perk->status; + $originalVisibility = $perk->visibility; + $timezone = $request->timezone; $startTime = $request->start_date . ' ' . $request->start_time; @@ -156,7 +166,7 @@ public function updatePerk(Request $request, $id) $endTimeCarbon = Carbon::createFromFormat('Y-m-d H:i:s', $endTime, $timezone); $endTimeCarbon->setTimezone('UTC'); - if ($startTimeCarbon->gt($endTimeCarbon)) { + if ($startTimeCarbon->gte($endTimeCarbon)) { return $this->errorResponse('End date must greater than start date', Response::HTTP_BAD_REQUEST); } @@ -228,6 +238,15 @@ public function updatePerk(Request $request, $id) $perk->visibility = $visibility; $perk->status = $status; $perk->save(); + + if ( + $perk->visibility == 'visible' && + $perk->status == 'active' && + ($perk->visibility != $originalVisibility || $perk->status != $originalStatus) + ) { + PerkNotification::dispatch($perk)->onQueue('default_long'); + } + return $this->successResponse($perk); } diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index f78e8fad..be8debea 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -100,116 +100,46 @@ public function getUserDashboard() { $settings = Helper::getSettings(); $current_era_id = (int) ($settings['current_era_id'] ?? 0); - // Define complete return object $return = [ - "node_rank" => 0, - "node_rank_total" => 100, - "total_stake" => 0, - "total_self_stake" => 0, - "total_delegators" => 0, - "uptime" => 0, - "eras_active" => 0, - "eras_since_bad_mark" => $current_era_id, - "total_bad_marks" => 0, - "update_responsiveness" => 100, - "peers" => 0, - "total_members" => 0, - "verified_members" => 0, - "association_members" => [], - "ranking" => [] + 'node_rank' => 0, + 'node_rank_total' => 0, + 'total_stake' => 0, + 'total_self_stake' => 0, + 'total_delegators' => 0, + 'uptime' => 0, + 'eras_active' => 0, + 'eras_since_bad_mark' => $current_era_id, + 'total_bad_marks' => 0, + 'update_responsiveness' => 100, + 'peers' => 0, + 'total_members' => 0, + 'verified_members' => 0, + 'association_members' => Helper::getActiveMembers(), + 'ranking' => [] ]; - // get all active members - $association_members = DB::select(" - SELECT - a.public_key, - c.id, c.pseudonym, c.node_status, - d.status, d.extra_status - FROM all_node_data2 AS a - JOIN user_addresses AS b - ON a.public_key = b.public_address_node - JOIN users AS c - ON b.user_id = c.id - JOIN profile AS d - ON c.id = d.user_id - WHERE a.era_id = $current_era_id and d.status = 'approved' - "); - $return["association_members"] = $association_members ?? []; - // get verified members count $verified_members = DB::select(" - SELECT a.pseudonym, b.status + SELECT count(a.id) as totalCount FROM users AS a JOIN profile AS b ON a.id = b.user_id WHERE b.status = 'approved' "); - $return["verified_members"] = $verified_members ? count($verified_members) : 0; + $return['verified_members'] = $verified_members[0]->totalCount ?? 0; // get total members count $total_members = DB::select(" - SELECT pseudonym + SELECT count(id) as totalCount FROM users WHERE role = 'member' "); - $return["total_members"] = $total_members ? count($total_members) : 0; - - // find rank - $ranking = DB::select(" - SELECT - public_key, uptime, - bid_delegators_count, - bid_delegation_rate, - bid_total_staked_amount - FROM all_node_data2 - WHERE era_id = $current_era_id - AND in_current_era = 1 - AND in_next_era = 1 - AND in_auction = 1 - "); - $max_delegators = 0; - $max_stake_amount = 0; - - foreach ($ranking as $r) { - if ((int) $r->bid_delegators_count > $max_delegators) { - $max_delegators = (int) $r->bid_delegators_count; - } - if ((int) $r->bid_total_staked_amount > $max_stake_amount) { - $max_stake_amount = (int) $r->bid_total_staked_amount; - } - } - - foreach ($ranking as $r) { - $uptime_score = (float) (25 * (float) $r->uptime / 100); - $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; - - $fee_score = 25 * (1 - (float) ((float) $r->bid_delegation_rate / 100)); - $fee_score = $fee_score < 0 ? 0 : $fee_score; - - $count_score = (float) ((float) $r->bid_delegators_count / $max_delegators) * 25; - $count_score = $count_score < 0 ? 0 : $count_score; - - $stake_score = (float) ((float) $r->bid_total_staked_amount / $max_stake_amount) * 25; - $stake_score = $stake_score < 0 ? 0 : $stake_score; - - $return["ranking"][$r->public_key] = $uptime_score + $fee_score + $count_score + $stake_score; - } - - uasort($return["ranking"], function($x, $y) { - if ($x == $y) { - return 0; - } - return ($x > $y) ? -1 : 1; - }); + $return['total_members'] = $total_members[0]->totalCount ?? 0; + + $rankingData = Helper::getRanking($current_era_id); - $sorted_ranking = []; - $i = 1; - foreach ($return["ranking"] as $public_key => $score) { - $sorted_ranking[$public_key] = $i; - $i += 1; - } - $return["ranking"] = $sorted_ranking; - $return["node_rank_total"] = count($sorted_ranking); + $return['ranking'] = $rankingData['ranking']; + $return['node_rank_total'] = $rankingData['node_rank_total']; // parse node addresses $addresses = DB::select(" @@ -236,65 +166,41 @@ public function getUserDashboard() { $baseObject = new \stdClass(); $baseObject->uptime = (float) ($address->uptime ?? 0); - $temp = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - ORDER BY era_id DESC - "); + $badMarkValues = Helper::calculateVariables('bad_marks_info', $p, $settings); + $total_bad_marks = (int) ($badMarkValues['total_bad_marks'] ?? 0); + $eras_since_bad_mark = (int) ($badMarkValues['eras_since_bad_mark'] ?? $current_era_id); + $return['total_bad_marks'] += $total_bad_marks; + if ($eras_since_bad_mark < $return['eras_since_bad_mark']) { + $return['eras_since_bad_mark'] = $eras_since_bad_mark; + } - $total_bad_marks = 0; - $eras_since_bad_mark = $current_era_id; - if ($temp && isset($temp[0])) { - $total_bad_marks = count($temp); - $eras_since_bad_mark = $current_era_id - (int) ($temp[0]->era_id ?? 0); - } - if ($eras_since_bad_mark < $return["eras_since_bad_mark"]) { - $return["eras_since_bad_mark"] = $eras_since_bad_mark; - } - - $temp = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - ORDER BY era_id ASC - LIMIT 1 - "); - $eras_active = 0; - if ($temp && isset($temp[0])) { - $eras_active = (int) ($temp[0]->era_id ?? 0); - } - if ($current_era_id - $eras_active > $return["eras_active"]) { - $return["eras_active"] = $current_era_id - $eras_active; - } + $min_era_id = Helper::calculateVariables('min_era', $p, $settings); + if ($current_era_id - $min_era_id > $return['eras_active']) { + $return['eras_active'] = $current_era_id - $min_era_id; + } $historical_performance = Helper::calculateUptime($baseObject, $p, $settings); if ( - array_key_exists($p, $return["ranking"]) && - ($return["node_rank"] == 0 || $return["ranking"][$p] < $return["node_rank"]) + array_key_exists($p, $return['ranking']) && + ($return['node_rank'] == 0 || $return['ranking'][$p] < $return['node_rank']) ) { - $return["node_rank"] = $return["ranking"][$p]; + $return['node_rank'] = $return['ranking'][$p]; } - $return["total_bad_marks"] += $total_bad_marks; - $return["total_stake"] += (int) ($address->bid_total_staked_amount ?? 0); - $return["total_self_stake"] += (int) ($address->bid_self_staked_amount ?? 0); - $return["total_delegators"] += (int) ($address->delegators ?? 0); - $return["peers"] += (int) ($address->peers ?? 0); - $return["uptime"] += $historical_performance; + $return['total_stake'] += (int) ($address->bid_total_staked_amount ?? 0); + $return['total_self_stake'] += (int) ($address->bid_self_staked_amount ?? 0); + $return['total_delegators'] += (int) ($address->delegators ?? 0); + $return['peers'] += (int) ($address->peers ?? 0); + $return['uptime'] += $historical_performance; } $addresses_count = count($addresses); if (!$addresses_count) $addresses_count = 1; - $return["uptime"] = round((float) ($return["uptime"] / $addresses_count), 2); + $return['uptime'] = round((float) ($return['uptime'] / $addresses_count), 2); - unset($return["ranking"]); + unset($return['ranking']); return $this->successResponse($return); } @@ -307,15 +213,15 @@ public function getMembershipPage() { $current_era_id = (int) ($settings['current_era_id'] ?? 0); $return = [ - "node_status" => $user->node_status ?? '', - "kyc_status" => "Not Verified", - "uptime" => [], - "avg_uptime" => 0, - "total_eras" => 0, - "eras_since_bad_mark" => $current_era_id, - "total_bad_marks" => 0, - "update_responsiveness" => 100, - "peers" => 0 + 'node_status' => $user->node_status ?? '', + 'kyc_status' => 'Not Verified', + 'uptime' => [], + 'avg_uptime' => 0, + 'total_eras' => 0, + 'eras_since_bad_mark' => $current_era_id, + 'total_bad_marks' => 0, + 'update_responsiveness' => 100, + 'peers' => 0 ]; if (isset($user->profile) && $user->profile->status == 'approved') { $return['kyc_status'] = 'Verified'; @@ -343,54 +249,29 @@ public function getMembershipPage() { $baseObject = new \stdClass(); $baseObject->uptime = (float) ($address->uptime ?? 0); - $temp = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - ORDER BY era_id DESC - "); - if (!$temp) $temp = []; + $badMarkValues = Helper::calculateVariables('bad_marks_info', $p, $settings); + $total_bad_marks = (int) ($badMarkValues['total_bad_marks'] ?? 0); + $eras_since_bad_mark = (int) ($badMarkValues['eras_since_bad_mark'] ?? $current_era_id); + $return['total_bad_marks'] += $total_bad_marks; + if ($eras_since_bad_mark < $return['eras_since_bad_mark']) { + $return['eras_since_bad_mark'] = $eras_since_bad_mark; + } - $eras_since_bad_mark = $current_era_id; - if ($temp && isset($temp[0])) { - $eras_since_bad_mark = $current_era_id - (int) ($temp[0]->era_id ?? 0); - } - $total_bad_marks = count($temp); - if ($eras_since_bad_mark < $return["eras_since_bad_mark"]) { - $return["eras_since_bad_mark"] = $eras_since_bad_mark; - } - - $temp = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - ORDER BY era_id ASC - LIMIT 1 - "); - if (!$temp) $temp = []; - $total_eras = 0; - if ($temp && isset($temp[0])) { - $total_eras = (int) ($temp[0]->era_id ?? 0); - } - if ($current_era_id - $total_eras > $return["total_eras"]) { - $return["total_eras"] = $current_era_id - $total_eras; - } + $min_era_id = Helper::calculateVariables('min_era', $p, $settings); + if ($current_era_id - $min_era_id > $return['total_eras']) { + $return['total_eras'] = $current_era_id - $min_era_id; + } $historical_performance = Helper::calculateUptime($baseObject, $p, $settings); - $return["total_bad_marks"] += $total_bad_marks; - $return["peers"] += (int) ($address->peers ?? 0); - $return["uptime"][$p] = $historical_performance; - $return["avg_uptime"] += $historical_performance; + $return['peers'] += (int) ($address->peers ?? 0); + $return['uptime'][$p] = $historical_performance; + $return['avg_uptime'] += $historical_performance; } $addresses_count = count($addresses); $addresses_count = $addresses_count ? $addresses_count : 1; - $return["avg_uptime"] = round((float) ($return["avg_uptime"] / $addresses_count), 2); + $return['avg_uptime'] = round((float) ($return['avg_uptime'] / $addresses_count), 2); return $this->successResponse($return); } @@ -404,69 +285,15 @@ public function getNodesPage() { $settings = Helper::getSettings(); $current_era_id = (int) ($settings['current_era_id'] ?? 0); - // Define complete return object $return = [ - "mbs" => 0, - "ranking" => [], - "addresses" => [] + 'mbs' => 0, + 'ranking' => [], + 'addresses' => [] ]; - // get ranking - $ranking = DB::select(" - SELECT - public_key, uptime, - bid_delegators_count, - bid_delegation_rate, - bid_total_staked_amount - FROM all_node_data2 - WHERE era_id = $current_era_id - AND in_current_era = 1 - AND in_next_era = 1 - AND in_auction = 1 - "); - if (!$ranking) $ranking = []; - - $max_delegators = 0; - $max_stake_amount = 0; - foreach ($ranking as $r) { - if ((int) $r->bid_delegators_count > $max_delegators) { - $max_delegators = (int)$r->bid_delegators_count; - } - if ((int) $r->bid_total_staked_amount > $max_stake_amount) { - $max_stake_amount = (int)$r->bid_total_staked_amount; - } - } - - foreach ($ranking as $r) { - $uptime_score = (float) (25 * (float) $r->uptime / 100); - $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; - - $fee_score = 25 * (1 - (float) ($r->bid_delegation_rate / 100)); - $fee_score = $fee_score < 0 ? 0 : $fee_score; + $rankingData = Helper::getRanking($current_era_id); - $count_score = (float) ($r->bid_delegators_count / $max_delegators) * 25; - $count_score = $count_score < 0 ? 0 : $count_score; - - $stake_score = (float) ($r->bid_total_staked_amount / $max_stake_amount) * 25; - $stake_score = $stake_score < 0 ? 0 : $stake_score; - - $return["ranking"][$r->public_key] = $uptime_score + $fee_score + $count_score + $stake_score; - } - - uasort($return["ranking"], function($x, $y) { - if ($x == $y) { - return 0; - } - return ($x > $y) ? -1 : 1; - }); - - $sorted_ranking = []; - $i = 1; - foreach ($return["ranking"] as $public_key => $score) { - $sorted_ranking[$public_key] = $i; - $i += 1; - } - $return["ranking"] = $sorted_ranking; + $return['ranking'] = $rankingData['ranking']; $addresses = DB::select(" SELECT @@ -500,36 +327,12 @@ public function getNodesPage() { $baseObject->uptime = (float) ($address->uptime ?? 0); $baseObject->mbs = $mbs; - $temp = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - ORDER BY era_id DESC - "); - if (!$temp) $temp = []; - - $eras_since_bad_mark = $current_era_id; - if (isset($temp[0])) { - $eras_since_bad_mark = $current_era_id - (int) ($temp[0]->era_id ?? 0); - } - $total_bad_marks = count($temp); - - $temp = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - ORDER BY era_id ASC - LIMIT 1 - "); - if (!$temp) $temp = []; - - $total_eras = 0; - if (isset($temp[0])) $total_eras = (int) ($temp[0]->era_id ?? 0); - if ($current_era_id > $total_eras) $total_eras = $current_era_id - $total_eras; + $badMarkValues = Helper::calculateVariables('bad_marks_info', $p, $settings); + $total_bad_marks = (int) ($badMarkValues['total_bad_marks'] ?? 0); + $eras_since_bad_mark = (int) ($badMarkValues['eras_since_bad_mark'] ?? $current_era_id); + + $min_era_id = Helper::calculateVariables('min_era', $p, $settings); + $total_eras = $current_era_id - $min_era_id; $historical_performance = Helper::calculateUptime($baseObject, $p, $settings); @@ -543,9 +346,7 @@ public function getNodesPage() { LIMIT 1 "); if (!$temp) $temp = []; - $daily_earning = 0; - if (isset($temp[0])) $daily_earning = (float) ($temp[0]->bid_self_staked_amount ?? 0); - $daily_earning = (float) $address->bid_self_staked_amount - $daily_earning; + $daily_earning = (float) $address->bid_self_staked_amount - (float) ($temp[0]->bid_self_staked_amount ?? 0); $daily_earning = $daily_earning < 0 ? 0 : $daily_earning; $earning_day = $nodeHelper->getValidatorRewards($p, 'day'); @@ -553,21 +354,21 @@ public function getNodesPage() { $earning_month = $nodeHelper->getValidatorRewards($p, 'month'); $earning_year = $nodeHelper->getValidatorRewards($p, 'year'); - $return["addresses"][$p] = [ - "stake_amount" => $address->bid_total_staked_amount, - "delegators" => $address->bid_delegators_count, - "uptime" => $historical_performance, - "update_responsiveness" => 100, - "peers" => (int) ($address->peers ?? 0), - "daily_earning" => $daily_earning, - "total_eras" => $total_eras, - "eras_since_bad_mark" => $eras_since_bad_mark, - "total_bad_marks" => $total_bad_marks, - "validator_rewards" => [ - "day" => $earning_day, - "week" => $earning_week, - "month" => $earning_month, - "year" => $earning_year + $return['addresses'][$p] = [ + 'stake_amount' => $address->bid_total_staked_amount, + 'delegators' => $address->bid_delegators_count, + 'uptime' => $historical_performance, + 'update_responsiveness' => 100, + 'peers' => (int) ($address->peers ?? 0), + 'daily_earning' => $daily_earning, + 'total_eras' => $total_eras, + 'eras_since_bad_mark' => $eras_since_bad_mark, + 'total_bad_marks' => $total_bad_marks, + 'validator_rewards' => [ + 'day' => $earning_day, + 'week' => $earning_week, + 'month' => $earning_month, + 'year' => $earning_year ] ]; } @@ -586,9 +387,8 @@ public function getMyEras() { // define return object $return = [ - "addresses" => [], - "column_count" => 2, - "eras" => [] + 'addresses' => [], + 'eras' => [] ]; // get addresses data @@ -605,64 +405,29 @@ public function getMyEras() { "); if (!$addresses) $addresses = []; - // get settings - $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; $uptime_calc_size = isset($settings['uptime_calc_size']) ? (int) $settings['uptime_calc_size'] : 1; - // fetch MBS - $mbs = DB::select(" - SELECT mbs - FROM mbs - WHERE era_id = $current_era_id - "); - $mbs = $mbs[0]->mbs ?? 0; - // for each member's node address foreach ($addresses as $address) { $p = $address->public_key ?? ''; $baseObject = new \stdClass(); $baseObject->uptime = (float) ($address->uptime ?? 0); - $baseObject->mbs = $mbs; - - $temp = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - ORDER BY era_id DESC - "); - if (!$temp) $temp = []; + + $badMarkValues = Helper::calculateVariables('bad_marks_info', $p, $settings); + $total_bad_marks = (int) ($badMarkValues['total_bad_marks'] ?? 0); + $eras_since_bad_mark = (int) ($badMarkValues['eras_since_bad_mark'] ?? $current_era_id); - $eras_since_bad_mark = $current_era_id; - if (isset($temp[0])) { - $eras_since_bad_mark = $current_era_id - (int) ($temp[0]->era_id ?? 0); - } - $total_bad_marks = count($temp); + $min_era_id = Helper::calculateVariables('min_era', $p, $settings); + $eras_active = $current_era_id - $min_era_id; - $temp = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - ORDER BY era_id ASC - LIMIT 1 - "); - if (!$temp) $temp = []; - - $eras_active = 0; - if (isset($temp[0])) $eras_active = (int) ($temp[0]->era_id ?? 0); - if ($eras_active < $current_era_id) $eras_active = $current_era_id - $eras_active; - $historical_performance = Helper::calculateUptime($baseObject, $p, $settings); - $return["addresses"][$p] = [ - "uptime" => round($historical_performance, 2), - "eras_active" => $eras_active, - "eras_since_bad_mark" => $eras_since_bad_mark, - "total_bad_marks" => $total_bad_marks + $return['addresses'][$p] = [ + 'uptime' => $historical_performance, + 'eras_active' => $eras_active, + 'eras_since_bad_mark' => $eras_since_bad_mark, + 'total_bad_marks' => $total_bad_marks ]; } @@ -698,29 +463,19 @@ public function getMyEras() { if (!isset($sorted_eras[$era_id])) { $sorted_eras[$era_id] = [ - "era_start_time" => $era_start_time, - "addresses" => [] + 'era_start_time' => $era_start_time, + 'addresses' => [] ]; } - $sorted_eras[$era_id]["addresses"][$public_key] = [ - "in_pool" => $era->in_auction, - "rewards" => $era->uptime + $sorted_eras[$era_id]['addresses'][$public_key] = [ + 'in_pool' => $era->in_auction, + 'rewards' => $era->uptime ?? 0 ]; } - $return["eras"] = $sorted_eras; - $column_count = 0; - - foreach ($return["eras"] as $era) { - $count = $era["addresses"] ? count($era["addresses"]) : 0; - if ($count > $column_count) { - $column_count = $count; - } - } - - $return["column_count"] = $column_count + 1; - + $return['eras'] = $sorted_eras; + return $this->successResponse($return); } @@ -749,7 +504,7 @@ public function updateShuftiproStatus() { if ($user) { $user_id = $user->id; $profile = Profile::where('user_id', $user_id)->first(); - + if ($profile) { $profile->status = null; $profile->save(); @@ -1528,12 +1283,10 @@ public function getVotes(Request $request) if ($status == 'active') { $query = Ballot::where('status', 'active') ->where('time_begin', '<=', $now); - } - else if ($status == 'scheduled') { + } else if ($status == 'scheduled') { $query = Ballot::where('status', 'active') ->where('time_begin', '>', $now); - } - else { + } else { $query = Ballot::where('status', '<>', 'active'); } @@ -1650,7 +1403,7 @@ public function canRequestReactivation() if ($temp) { // Check Redmarks if ($redmarks_revoke > 0) { - $bad_marks = Helper::calculateBadMarks($temp, $public_address_node, $settings); + $bad_marks = Helper::calculateBadMarksRevoke($temp, $public_address_node, $settings); if ($bad_marks > $redmarks_revoke) { $flag = false; } @@ -1693,18 +1446,10 @@ public function canVote() 'can_vote' => false ]; - $current_era_id = Helper::getCurrentERAId(); $settings = Helper::getSettings(); - - $voting_eras_to_vote = - isset($settings['voting_eras_to_vote']) ? - (int) $settings['voting_eras_to_vote'] : - 1; - - $voting_eras_since_redmark = - isset($settings['voting_eras_since_redmark']) ? - (int) $settings['voting_eras_since_redmark'] : - 1; + + $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; + $voting_eras_since_redmark = isset($settings['voting_eras_since_redmark']) ? (int) $settings['voting_eras_since_redmark'] : 1; $return['setting_voting_eras'] = $voting_eras_to_vote; $return['setting_good_standing_eras'] = $voting_eras_since_redmark; @@ -1719,46 +1464,15 @@ public function canVote() foreach ($user_addresses as $a) { $p = $a->public_address_node ?? ''; - // find smallest number of eras since public_key encountered a bad mark - $temp = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - ORDER BY era_id DESC - LIMIT 1 - "); - - if (!$temp) { - $temp = []; - } - - $good_standing_eras = $current_era_id - (int) ($temp[0]->era_id ?? 0); - - if ($good_standing_eras > $return['good_standing_eras']) { - $return['good_standing_eras'] = $good_standing_eras; - } - - // total_active_eras - $total_active_eras = DB::select(" - SELECT count(id) as tCount - FROM all_node_data2 - WHERE public_key = '$p' - AND in_current_era = 1 - AND bid_inactive = 0 - "); - - $total_active_eras = $total_active_eras[0] ?? array(); - $return['total_active_eras'] = (int)($total_active_eras->tCount ?? 0); + $return['good_standing_eras'] = Helper::calculateVariables('good_standing_eras', $p, $settings); + $return['total_active_eras'] = Helper::calculateVariables('total_active_eras', $p, $settings); if ( - $return['total_active_eras'] >= $voting_eras_to_vote && + $return['total_active_eras'] >= $voting_eras_to_vote && $return['good_standing_eras'] >= $voting_eras_since_redmark ) { $return['can_vote'] = true; + break; } } @@ -1774,12 +1488,7 @@ public function vote($id, Request $request) $vote = $request->vote; - if ( - !$vote || ( - $vote != 'for' && - $vote != 'against' - ) - ) { + if (!$vote || ($vote != 'for' && $vote != 'against')) { return $this->errorResponse( 'Paramater invalid (vote is for or against)', Response::HTTP_BAD_REQUEST @@ -1791,69 +1500,27 @@ public function vote($id, Request $request) $addresses = UserAddress::where('user_id', $user->id)->get(); if (!$addresses) { - $addresses = array(); + $addresses = []; } - $current_era_id = Helper::getCurrentERAId(); - - // get settings - $voting_eras_to_vote = DB::select(" - SELECT value - FROM settings - WHERE name = 'voting_eras_to_vote' - "); - $voting_eras_to_vote = $voting_eras_to_vote[0] ?? array(); - $voting_eras_to_vote = (int)($voting_eras_to_vote->value ?? 0); + $settings = Helper::getSettings(); + $current_era_id = (int) ($settings['current_era_id'] ?? 0); - $voting_eras_since_redmark = DB::select(" - SELECT value - FROM settings - WHERE name = 'voting_eras_since_redmark' - "); - $voting_eras_since_redmark = $voting_eras_since_redmark[0] ?? array(); - $voting_eras_since_redmark = (int)($voting_eras_since_redmark->value ?? 0); + $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; + $voting_eras_since_redmark = isset($settings['voting_eras_since_redmark']) ? (int) $settings['voting_eras_since_redmark'] : 1; foreach ($addresses as $address) { $p = $address->public_address_node ?? ''; - // find smallest number of eras since public_key encountered a bad mark - // good_standing_eras - $good_standing_eras = DB::select(" - SELECT era_id - FROM all_node_data2 - WHERE public_key = '$p' - AND ( - in_current_era = 0 OR - bid_inactive = 1 - ) - ORDER BY era_id DESC - LIMIT 1 - "); - $good_standing_eras = $good_standing_eras[0] ?? array(); - $good_standing_eras = (int)($good_standing_eras->era_id ?? 0); - $good_standing_eras = $current_era_id - $good_standing_eras; - - if ($good_standing_eras < 0) { - $good_standing_eras = 0; - } - - // total_active_eras - $total_active_eras = DB::select(" - SELECT count(id) AS tCount - FROM all_node_data2 - WHERE public_key = '$p' - AND in_current_era = 1 - AND bid_inactive = 0 - "); - - $total_active_eras = $total_active_eras[0] ?? array(); - $total_active_eras = (int)($total_active_eras->tCount ?? 0); + $good_standing_eras = Helper::calculateVariables('good_standing_eras', $p, $settings); + $total_active_eras = Helper::calculateVariables('total_active_eras', $p, $settings); if ( - $total_active_eras >= $voting_eras_to_vote && + $total_active_eras >= $voting_eras_to_vote && $good_standing_eras >= $voting_eras_since_redmark ) { $stable = true; + break; } } @@ -2000,9 +1667,13 @@ public function uploadAvatar(Request $request) public function getMembers(Request $request) { - $current_era_id = Helper::getCurrentERAId(); + $settings = Helper::getSettings(); + $current_era_id = (int) ($settings['current_era_id'] ?? 0); + $search = $request->search; + $sort_key = $request->sort_key ?? 'created_at'; + $members = DB::table('users') ->select( 'users.id', @@ -2012,7 +1683,7 @@ public function getMembers(Request $request) 'users.kyc_verified_at', 'user_addresses.node_verified_at', 'all_node_data2.public_key', - 'all_node_data2.uptime', + 'all_node_data2.uptime as uptimeBase', 'all_node_data2.bid_delegators_count', 'all_node_data2.bid_delegation_rate', 'all_node_data2.bid_total_staked_amount' @@ -2056,214 +1727,22 @@ public function getMembers(Request $request) } foreach ($members as &$member) { - $uptime_score = ( - ($request->uptime ?? 0) * - (float) ($member->historical_performance ?? 0) - ) / 100; - $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; - - $fee_score = ( - ($request->delegation_rate ?? 0) * - (1 - ((float)$member->bid_delegation_rate / 100)) - ); - $fee_score = $fee_score < 0 ? 0 : $fee_score; - - $count_score = ( - (float)$member->bid_delegators_count / - $max_delegators - ) * ($request->delegators ?? 0); - $count_score = $count_score < 0 ? 0 : $count_score; - - $stake_score = ( - (float)$member->bid_total_staked_amount / - $max_stake_amount - ) * ($request->stake_amount ?? 0); - $stake_score = $stake_score < 0 ? 0 : $stake_score; - - $member->total_score = ( - $uptime_score + - $fee_score + - $count_score + - $stake_score - ); - } - - return $this->successResponse($members); - - /* - $limit = $request->limit ?? 50; + $baseObject = $member; + $baseObject->uptime = $member->uptimeBase; + $member->uptime = Helper::calculateUptime($member, $member->public_key, $settings); - $slide_value_uptime = $request->uptime ?? 0; - $slide_value_update_responsiveness = $request->update_responsiveness ?? 0; - $slide_value_delegotors = $request->delegators ?? 0; - $slide_value_stake_amount = $request->stake_amount ?? 0; - $slide_delegation_rate = $request->delegation_rate ?? 0; - - $max_uptime = Node::max('uptime'); - $max_uptime = $max_uptime * 100; - $max_delegators = NodeInfo::max('delegators_count'); + $r = $member; + $r->uptime_rate = (float) ($request->uptime ?? 0); + $r->fee_rate = (float) ($request->delegation_rate ?? 0); + $r->count_rate = (float) ($request->delegators ?? 0); + $r->stake_rate = (float) ($request->stake_amount ?? 0); - if(!$max_delegators || $max_delegators < 1) { - $max_delegators = 1; + $member->totalScore = Helper::getTotalScore($r, $max_delegators, $max_stake_amount); } - $max_stake_amount = NodeInfo::max('total_staked_amount'); - - if(!$max_stake_amount || $max_stake_amount < 1) { - $max_stake_amount = 1; - } - - $sort_key = $request->sort_key ?? 'created_at'; - - $users = User::with(['metric', 'nodeInfo', 'profile']) - ->whereHas('nodeInfo') - ->where('role', 'member') - ->where(function ($query) use ($search) { - if ($search) { - $query->where('users.first_name', 'like', '%' . $search . '%') - ->orWhere('users.last_name', 'like', '%' . $search . '%'); - } - }) - ->get(); - - foreach ($users as $user) { - unset($user['email_verified_at']); - unset($user['last_login_at']); - unset($user['last_login_ip_address']); - unset($user['twoFA_login']); - unset($user['twoFA_login_active']); - - $latest = Node::where( - 'node_address', - strtolower($user->public_address_node) - ) - ->whereNotnull('protocol_version') - ->orderBy('created_at', 'desc') - ->first(); - - if (!$latest) { - $latest = new Node(); - } - - $user->status = ( - isset($user->profile) && - isset($user->profile->status) ? - $user->profile->status : '' - ); - - $uptime_nodeInfo = $user->nodeInfo->uptime; - $uptime_node = ( - isset($latest->uptime) && - $latest->uptime ? - $latest->uptime * 100 : - null - ); - - $uptime_metric = ( - isset($user->metric) && - isset($user->metric->uptime) ? - $user->metric->uptime : - null - ); - - $res_nodeInfo = $user->nodeInfo->update_responsiveness ?? null; - $res_node = $latest->update_responsiveness ?? null; - $res_metric = $user->metric->update_responsiveness ?? null; - - $uptime = ( - $uptime_nodeInfo ? - $uptime_nodeInfo : ( - $uptime_node ? - $uptime_node : ( - $uptime_metric ? - $uptime_metric : - 1 - ) - ) - ); - - $res = ( - $res_nodeInfo ? - $res_nodeInfo : ( - $res_node ? - $res_node : ( - $res_metric ? - $res_metric : - 0 - ) - ) - ); - - $delegation_rate = ( - isset($user->nodeInfo->delegation_rate) && - $user->nodeInfo->delegation_rate ? - $user->nodeInfo->delegation_rate / 100 : - 1 - ); - - if ($delegation_rate > 1) { - $delegation_rate = 1; - } - - $delegators_count = ( - isset($user->nodeInfo->delegators_count) && - $user->nodeInfo->delegators_count ? - $user->nodeInfo->delegators_count : - 0 - ); - - $total_staked_amount = ( - isset($user->nodeInfo->total_staked_amount) && - $user->nodeInfo->total_staked_amount ? - $user->nodeInfo->total_staked_amount : - 0 - ); - - $uptime_score = (float)( - ($slide_value_uptime * $uptime) / - 100 - ); - - $delegation_rate_score = (float)( - ( - $slide_delegation_rate * - (1 - $delegation_rate) - ) / - 100 - ); - - $delegators_count_score = (float)( - ($delegators_count / $max_delegators) * - $slide_value_delegotors - ); - - $total_staked_amount_score = (float)( - ($total_staked_amount / $max_stake_amount) * - $slide_value_stake_amount - ); - - $res_score = (float)( - ($slide_value_update_responsiveness * $res) / - 100 - ); - - $user->uptime = $uptime; - $user->delegation_rate = $delegation_rate; - $user->delegators_count = $delegators_count; - $user->total_staked_amount = $total_staked_amount; - $user->totalScore = ( - $uptime_score + - $delegation_rate_score + - $delegators_count_score + - $total_staked_amount_score + - $res_score - ); - } + $members = $members->sortByDesc($sort_key)->values(); - $users = $users->sortByDesc($sort_key)->values(); - $users = Helper::paginate($users, $limit, $request->page); - return $this->successResponse($users); - */ + return $this->successResponse($members); } public function getMemberDetail($id, Request $request) { @@ -2668,13 +2147,14 @@ public function getLockRules() $ruleKycNotVerify1 = array_map( function ($object) { return $object->screen; - }, + }, $ruleKycNotVerify->all() ); $ruleStatusIsPoor = LockRules::where('type', 'status_is_poor') ->where('is_lock', 1) - ->orderBy('id', 'ASC')->select(['id', 'screen']) + ->orderBy('id', 'ASC') + ->select(['id', 'screen']) ->get(); $ruleStatusIsPoor1 = array_map( @@ -2726,147 +2206,6 @@ public function getListNodesBy(Request $request) ]); } - /* - public function getListNodes(Request $request) - { - $user = auth()->user(); - $user_id = $user->id; - - $current_era_id = DB::select(" - SELECT era_id - FROM all_node_data2 - ORDER BY era_id DESC - LIMIT 1 - "); - $current_era_id = (int) ($current_era_id[0]->era_id ?? 0); - - // define return object - $return = array( - "nodes" => array(), - "ranking" => array(), - "node_rank_total" => 100 - ); - - $return["nodes"] = DB::select(" - SELECT - a.public_address_node, - b.id, - b.pseudonym, - c.blockchain_name, - c.blockchain_desc - FROM user_addresses AS a - JOIN users AS b - ON a.user_id = b.id - JOIN profile AS c - ON b.id = c.user_id - WHERE b.id = $user_id - "); - - // find rank - $ranking = DB::select(" - SELECT - public_key, uptime, - bid_delegators_count, - bid_delegation_rate, - bid_total_staked_amount - FROM all_node_data2 - WHERE era_id = $current_era_id - AND in_current_era = 1 - AND in_next_era = 1 - AND in_auction = 1 - "); - $max_delegators = 0; - $max_stake_amount = 0; - - foreach ($ranking as $r) { - if ((int)$r->bid_delegators_count > $max_delegators) { - $max_delegators = (int)$r->bid_delegators_count; - } - - if ((int)$r->bid_total_staked_amount > $max_stake_amount) { - $max_stake_amount = (int)$r->bid_total_staked_amount; - } - } - - foreach ($ranking as $r) { - $uptime_score = ( - 25 * (float)$r->uptime - ) / 100; - $uptime_score = $uptime_score < 0 ? 0 : $uptime_score; - - $fee_score = ( - 25 * - (1 - ((float)$r->bid_delegation_rate / 100)) - ); - $fee_score = $fee_score < 0 ? 0 : $fee_score; - - $count_score = ( - (float)$r->bid_delegators_count / - $max_delegators - ) * 25; - $count_score = $count_score < 0 ? 0 : $count_score; - - $stake_score = ( - (float)$r->bid_total_staked_amount / - $max_stake_amount - ) * 25; - $stake_score = $stake_score < 0 ? 0 : $stake_score; - - $return["ranking"][$r->public_key] = ( - $uptime_score + - $fee_score + - $count_score + - $stake_score - ); - } - - uasort( - $return["ranking"], - function($x, $y) { - if ($x == $y) { - return 0; - } - - return ($x > $y) ? -1 : 1; - } - ); - - $sorted_ranking = array(); - $i = 1; - - foreach ($return["ranking"] as $public_key => $score) { - $sorted_ranking[$public_key] = $i; - $i += 1; - } - - $return["ranking"] = $sorted_ranking; - $return["node_rank_total"] = count($sorted_ranking); - - return $this->successResponse($return); - - - $limit = $request->limit ?? 50; - - $nodes = UserAddress::select([ - 'users.id as user_id', - 'users.pseudonym', - 'user_addresses.public_address_node', - 'user_addresses.is_fail_node', - 'user_addresses.rank', - 'profile.blockchain_name', - 'profile.blockchain_desc', - ]) - ->leftJoin('users', 'users.id', '=', 'user_addresses.user_id') - ->leftJoin('profile', 'profile.user_id', '=', 'users.id') - ->where('users.banned', 0) - ->whereNotNull('users.public_address_node') - ->orderBy('user_addresses.rank', 'asc') - ->paginate($limit); - - return $this->successResponse($nodes); - } - */ - public function infoDashboard() { $user = auth()->user(); @@ -2895,6 +2234,7 @@ public function infoDashboard() $response['rank'] = $user->rank; $response['delegators'] = $delegators; $response['stake_amount'] = $stake_amount; + return $this->successResponse($response); } diff --git a/app/Http/EmailerHelper.php b/app/Http/EmailerHelper.php index 69205eba..5ce8118a 100644 --- a/app/Http/EmailerHelper.php +++ b/app/Http/EmailerHelper.php @@ -16,7 +16,7 @@ public static function getEmailerData() { 'admins' => [], 'triggerAdmin' => [], 'triggerUser' => [], - 'triggerMember' => [] + 'triggerMember' => [] ]; $admins = EmailerAdmin::where('id', '>', 0)->orderBy('email', 'asc')->get(); @@ -50,40 +50,53 @@ public static function getEmailerData() { } // Send Admin Email - public static function triggerAdminEmail($title, $emailerData, $user = null) { - if (count($emailerData['admins'] ?? [])) { - $item = $emailerData['triggerAdmin'][$title] ?? null; - if ($item) { - $content = $item['content']; - $subject =$item['subject']; - if ($user) { - $name = $user->first_name . ' ' . $user->last_name; - $content = str_replace('[name]', $name, $content); - $subject = str_replace('[name]', $name, $subject); - $content = str_replace('[email]', $user->email, $content); - $subject = str_replace('[email]', $user->email, $subject); - } - Mail::to($emailerData['admins'])->send(new AdminAlert($subject, $content)); - } - } - } + public static function triggerAdminEmail($title, $emailerData, $user = null) { + if (count($emailerData['admins'] ?? [])) { + $item = $emailerData['triggerAdmin'][$title] ?? null; + if ($item) { + $content = $item['content']; + $subject =$item['subject']; + if ($user) { + $name = $user->first_name . ' ' . $user->last_name; + $content = str_replace('[name]', $name, $content); + $subject = str_replace('[name]', $name, $subject); + $content = str_replace('[email]', $user->email, $content); + $subject = str_replace('[email]', $user->email, $subject); + } + Mail::to($emailerData['admins'])->send(new AdminAlert($subject, $content)); + } + } + } - // Send User Email - public static function triggerUserEmail($to, $title, $emailerData, $user = null, $userAddress = null) { - $item = $emailerData['triggerUser'][$title] ?? null; - if ($item) { + // Send User Email + public static function triggerUserEmail($to, $title, $emailerData, $user = null, $userAddress = null, $extraOptions = []) { + $item = $emailerData['triggerUser'][$title] ?? null; + if ($item) { $content = $item['content']; $subject = $item['subject']; - if ($user) { - $name = $user->first_name . ' ' . $user->last_name; - $content = str_replace('[name]', $name, $content); - $subject = str_replace('[name]', $name, $subject); - $content = str_replace('[email]', $user->email, $content); - if ($userAddress) $content = str_replace('[node address]', $userAddress->public_address_node, $content); - else $content = str_replace('[node address]', $user->public_address_node, $content); - $subject = str_replace('[email]', $user->email, $subject); - } - Mail::to($to)->send(new UserAlert($subject, $content)); - } - } + if ($user) { + if (isset($user->first_name) && isset($user->last_name)) { + $name = $user->first_name . ' ' . $user->last_name; + $subject = str_replace('[name]', $name, $subject); + $content = str_replace('[name]', $name, $content); + } + if (isset($user->email)) { + $subject = str_replace('[email]', $user->email, $subject); + $content = str_replace('[email]', $user->email, $content); + } + if (isset($extraOptions['perk_title'])) { + $content = str_replace('[perk]', $extraOptions['perk_title'], $content); + } + if (isset($extraOptions['vote_title'])) { + $content = str_replace('[vote]', $extraOptions['vote_title'], $content); + } + if ($userAddress && isset($userAddress->public_address_node)) { + $content = str_replace('[node address]', $userAddress->public_address_node, $content); + } else if (isset($user->public_address_node)) { + $content = str_replace('[node address]', $user->public_address_node, $content); + } + } + Mail::to($to)->send(new UserAlert($subject, $content)); + } + } } \ No newline at end of file diff --git a/app/Jobs/BallotNotification.php b/app/Jobs/BallotNotification.php new file mode 100644 index 00000000..35a00e3a --- /dev/null +++ b/app/Jobs/BallotNotification.php @@ -0,0 +1,82 @@ +ballot = $ballot; + } + + public function handle() { + $settings = Helper::getSettings(); + $members = Helper::getActiveMembers(); + + $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; + $voting_eras_since_redmark = isset($settings['voting_eras_since_redmark']) ? (int) $settings['voting_eras_since_redmark'] : 1; + + if ($members && count($members) > 0) { + $emails = $processed = []; + foreach ($members as $member) { + $userId = (int) $member->id; + $email = $member->email; + + if (!in_array($email, $emails) && !in_array($userId, $processed)) { + $canVote = false; + $processed[] = $userId; + + $userAddresses = UserAddress::select('public_address_node') + ->where('user_id', $userId) + ->get(); + if ($userAddresses && count($userAddresses) > 0) { + foreach ($userAddresses as $addressRecord) { + $p = $addressRecord->public_address_node ?? ''; + + $good_standing_eras = Helper::calculateVariables('good_standing_eras', $p, $settings); + $total_active_eras = Helper::calculateVariables('total_active_eras', $p, $settings); + + if ( + $total_active_eras >= $voting_eras_to_vote && + $good_standing_eras >= $voting_eras_since_redmark + ) { + $canVote = true; + break; + } + } + } + + if ($canVote) { + $emails[] = $email; + } + } + } + if (count($emails) > 0) { + $extraOptions = [ + 'vote_title' => $this->ballot->title ?? '' + ]; + $emailerData = EmailerHelper::getEmailerData(); + foreach ($emails as $email) { + $user = new \stdClass(); + $user->email = $email; + EmailerHelper::triggerUserEmail($email, 'New Vote Started', $emailerData, $user, null, $extraOptions); + } + } + } + } +} \ No newline at end of file diff --git a/app/Jobs/BallotReminder24.php b/app/Jobs/BallotReminder24.php new file mode 100644 index 00000000..eea03cbd --- /dev/null +++ b/app/Jobs/BallotReminder24.php @@ -0,0 +1,100 @@ +ballot = $ballot; + } + + public function handle() { + if (!$this->ballot) return; + + $voteResults = $this->ballot->voteResults ?? []; + $voteUsers = []; + if ($voteResults && count($voteResults) > 0) { + foreach ($voteResults as $item) { + $voteUsers[] = (int) $item->user_id; + } + } + + $settings = Helper::getSettings(); + $members = Helper::getActiveMembers(); + + $voting_eras_to_vote = isset($settings['voting_eras_to_vote']) ? (int) $settings['voting_eras_to_vote'] : 1; + $voting_eras_since_redmark = isset($settings['voting_eras_since_redmark']) ? (int) $settings['voting_eras_since_redmark'] : 1; + + if ($members && count($members) > 0) { + $emails = $processed = []; + foreach ($members as $member) { + $userId = (int) $member->id; + $email = $member->email; + + if ( + !in_array($email, $emails) && + !in_array($userId, $processed) && + !in_array($userId, $voteUsers) + ) { + $canVote = false; + $processed[] = $userId; + + $userAddresses = UserAddress::select('public_address_node') + ->where('user_id', $userId) + ->get(); + if ($userAddresses && count($userAddresses) > 0) { + foreach ($userAddresses as $addressRecord) { + $p = $addressRecord->public_address_node ?? ''; + + $good_standing_eras = Helper::calculateVariables('good_standing_eras', $p, $settings); + $total_active_eras = Helper::calculateVariables('total_active_eras', $p, $settings); + + if ( + $total_active_eras >= $voting_eras_to_vote && + $good_standing_eras >= $voting_eras_since_redmark + ) { + $canVote = true; + break; + } + } + } + + if ($canVote) { + $emails[] = $email; + } + } + } + + if (count($emails) > 0) { + $extraOptions = [ + 'vote_title' => $this->ballot->title ?? '' + ]; + $emailerData = EmailerHelper::getEmailerData(); + foreach ($emails as $email) { + $user = new \stdClass(); + $user->email = $email; + EmailerHelper::triggerUserEmail($email, '24hr Vote Reminder', $emailerData, $user, null, $extraOptions); + } + } + } + + $this->ballot->reminder_24_sent = true; + $this->ballot->save(); + } +} \ No newline at end of file diff --git a/app/Jobs/PerkNotification.php b/app/Jobs/PerkNotification.php new file mode 100644 index 00000000..a548dbac --- /dev/null +++ b/app/Jobs/PerkNotification.php @@ -0,0 +1,48 @@ +perk = $perk; + } + + public function handle() { + $members = Helper::getActiveMembers(); + if ($members && count($members) > 0) { + $emails = []; + foreach ($members as $member) { + $email = $member->email; + if (!in_array($email, $emails)) { + $emails[] = $email; + } + } + if (count($emails) > 0) { + $extraOptions = [ + 'perk_title' => $this->perk->title ?? '' + ]; + $emailerData = EmailerHelper::getEmailerData(); + foreach ($emails as $email) { + $user = new \stdClass(); + $user->email = $email; + EmailerHelper::triggerUserEmail($email, 'New Perk Created', $emailerData, $user, null, $extraOptions); + } + } + } + } +} diff --git a/app/Providers/HorizonServiceProvider.php b/app/Providers/HorizonServiceProvider.php new file mode 100644 index 00000000..08881175 --- /dev/null +++ b/app/Providers/HorizonServiceProvider.php @@ -0,0 +1,42 @@ +email, [ + // + ]); + }); + } +} diff --git a/composer.json b/composer.json index 5d099e31..cab9fd94 100644 --- a/composer.json +++ b/composer.json @@ -13,11 +13,13 @@ "guzzlehttp/guzzle": "^7.4.3", "hellosign/hellosign-php-sdk": "^3.7", "laravel/framework": "^8.40", + "laravel/horizon": "^5.10", "laravel/passport": "^10.1", "laravel/tinker": "^2.5", + "make-software/casper-php-sdk": "dev-master", "phpseclib/phpseclib": "~3.0", - "stripe/stripe-php": "^7.121", - "make-software/casper-php-sdk": "dev-master" + "predis/predis": "^2.0", + "stripe/stripe-php": "^7.121" }, "require-dev": { "facade/ignition": "^2.5", diff --git a/config/app.php b/config/app.php index f9034603..6c4bde74 100644 --- a/config/app.php +++ b/config/app.php @@ -175,6 +175,7 @@ App\Providers\AuthServiceProvider::class, // App\Providers\BroadcastServiceProvider::class, App\Providers\EventServiceProvider::class, + App\Providers\HorizonServiceProvider::class, App\Providers\RouteServiceProvider::class, ], diff --git a/config/horizon.php b/config/horizon.php new file mode 100644 index 00000000..01a95229 --- /dev/null +++ b/config/horizon.php @@ -0,0 +1,230 @@ + env('HORIZON_DOMAIN'), + + /* + |-------------------------------------------------------------------------- + | Horizon Path + |-------------------------------------------------------------------------- + | + | This is the URI path where Horizon will be accessible from. Feel free + | to change this path to anything you like. Note that the URI will not + | affect the paths of its internal API that aren't exposed to users. + | + */ + + 'path' => env('HORIZON_PATH', 'horizon'), + + /* + |-------------------------------------------------------------------------- + | Horizon Redis Connection + |-------------------------------------------------------------------------- + | + | This is the name of the Redis connection where Horizon will store the + | meta information required for it to function. It includes the list + | of supervisors, failed jobs, job metrics, and other information. + | + */ + + 'use' => 'default', + + /* + |-------------------------------------------------------------------------- + | Horizon Redis Prefix + |-------------------------------------------------------------------------- + | + | This prefix will be used when storing all Horizon data in Redis. You + | may modify the prefix when you are running multiple installations + | of Horizon on the same server so that they don't have problems. + | + */ + + 'prefix' => env( + 'HORIZON_PREFIX', + Str::slug(env('APP_NAME', 'laravel'), '_').'_horizon:' + ), + + /* + |-------------------------------------------------------------------------- + | Horizon Route Middleware + |-------------------------------------------------------------------------- + | + | These middleware will get attached onto each Horizon route, giving you + | the chance to add your own middleware to this list or change any of + | the existing middleware. Or, you can simply stick with this list. + | + */ + + 'middleware' => ['web'], + + /* + |-------------------------------------------------------------------------- + | Queue Wait Time Thresholds + |-------------------------------------------------------------------------- + | + | This option allows you to configure when the LongWaitDetected event + | will be fired. Every connection / queue combination may have its + | own, unique threshold (in seconds) before this event is fired. + | + */ + + 'waits' => [ + 'redis:default' => 60, + ], + + /* + |-------------------------------------------------------------------------- + | Job Trimming Times + |-------------------------------------------------------------------------- + | + | Here you can configure for how long (in minutes) you desire Horizon to + | persist the recent and failed jobs. Typically, recent jobs are kept + | for one hour while all failed jobs are stored for an entire week. + | + */ + + 'trim' => [ + 'recent' => 60, + 'pending' => 60, + 'completed' => 60, + 'recent_failed' => 10080, + 'failed' => 10080, + 'monitored' => 10080, + ], + + /* + |-------------------------------------------------------------------------- + | Metrics + |-------------------------------------------------------------------------- + | + | Here you can configure how many snapshots should be kept to display in + | the metrics graph. This will get used in combination with Horizon's + | `horizon:snapshot` schedule to define how long to retain metrics. + | + */ + + 'metrics' => [ + 'trim_snapshots' => [ + 'job' => 24, + 'queue' => 24, + ], + ], + + /* + |-------------------------------------------------------------------------- + | Fast Termination + |-------------------------------------------------------------------------- + | + | When this option is enabled, Horizon's "terminate" command will not + | wait on all of the workers to terminate unless the --wait option + | is provided. Fast termination can shorten deployment delay by + | allowing a new instance of Horizon to start while the last + | instance will continue to terminate each of its workers. + | + */ + + 'fast_termination' => false, + + /* + |-------------------------------------------------------------------------- + | Memory Limit (MB) + |-------------------------------------------------------------------------- + | + | This value describes the maximum amount of memory the Horizon master + | supervisor may consume before it is terminated and restarted. For + | configuring these limits on your workers, see the next section. + | + */ + + 'memory_limit' => 64, + + /* + |-------------------------------------------------------------------------- + | Queue Worker Configuration + |-------------------------------------------------------------------------- + | + | Here you may define the queue worker settings used by your application + | in all environments. These supervisors and settings handle all your + | queued jobs and will be provisioned by Horizon during deployment. + | + */ + + 'defaults' => [ + 'supervisor-1' => [ + 'connection' => 'redis', + 'queue' => ['default'], + // 'balance' => 'auto', + 'balance' => 'simple', + 'balanceMaxShift' => 1, + 'balanceCooldown' => 3, + 'maxTime' => 0, + 'maxJobs' => 0, + 'memory' => 128, + 'tries' => 1, + 'timeout' => 60, + 'nice' => 0, + ], + 'supervisor-long-running' => [ + 'connection' => 'redis-long-running', + 'queue' => ['default_long'], + 'balance' => 'simple', + 'balanceMaxShift' => 1, + 'balanceCooldown' => 3, + 'maxTime' => 0, + 'maxJobs' => 0, + 'memory' => 128, + 'tries' => 1, + 'timeout' => 3600 + ], + ], + + 'environments' => [ + 'production' => [ + 'supervisor-1' => [ + 'maxProcesses' => 10, + 'processes' => 10 + ], + 'supervisor-long-running' => [ + 'maxProcesses' => 10, + 'processes' => 10 + ] + ], + + 'staging' => [ + 'supervisor-1' => [ + 'maxProcesses' => 10, + 'processes' => 10 + ], + 'supervisor-long-running' => [ + 'maxProcesses' => 5, + 'processes' => 5 + ] + ], + + 'local' => [ + 'supervisor-1' => [ + 'maxProcesses' => 3, + 'processes' => 3 + ], + 'supervisor-long-running' => [ + 'maxProcesses' => 1, + 'processes' => 1 + ] + ], + ], +]; diff --git a/config/queue.php b/config/queue.php index 25ea5a81..2c966f86 100644 --- a/config/queue.php +++ b/config/queue.php @@ -70,7 +70,15 @@ 'block_for' => null, 'after_commit' => false, ], - + + 'redis-long-running' => [ + 'driver' => 'redis', + 'connection' => 'default', + 'queue' => 'default_long', + 'retry_after' => 3800, + 'block_for' => null, + 'after_commit' => false, + ], ], /* diff --git a/database/migrations/2022_11_20_165842_create_jobs_table.php b/database/migrations/2022_11_20_165842_create_jobs_table.php new file mode 100644 index 00000000..1be9e8a8 --- /dev/null +++ b/database/migrations/2022_11_20_165842_create_jobs_table.php @@ -0,0 +1,36 @@ +bigIncrements('id'); + $table->string('queue')->index(); + $table->longText('payload'); + $table->unsignedTinyInteger('attempts'); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('jobs'); + } +} diff --git a/database/migrations/2022_11_21_022031_udpate_table_ballot_4.php b/database/migrations/2022_11_21_022031_udpate_table_ballot_4.php new file mode 100644 index 00000000..577fb199 --- /dev/null +++ b/database/migrations/2022_11_21_022031_udpate_table_ballot_4.php @@ -0,0 +1,15 @@ +boolean('reminder_24_sent')->default(false); + }); + } + public function down() { + // + } +} \ No newline at end of file diff --git a/database/migrations/2022_11_21_165620_update_mbs.php b/database/migrations/2022_11_21_165620_update_mbs.php new file mode 100644 index 00000000..0eb0a2e9 --- /dev/null +++ b/database/migrations/2022_11_21_165620_update_mbs.php @@ -0,0 +1,19 @@ +float('mbs', 20, 2)->change(); + }); + } + + public function down() + { + // + } +} diff --git a/public/vendor/horizon/app-dark.css b/public/vendor/horizon/app-dark.css new file mode 100644 index 00000000..00520878 --- /dev/null +++ b/public/vendor/horizon/app-dark.css @@ -0,0 +1,8 @@ +@charset "UTF-8";.vjs-tree{font-family:Monaco,Menlo,Consolas,Bitstream Vera Sans Mono,monospace!important}.vjs-tree.is-root{position:relative}.vjs-tree .vjs-tree__content{padding-left:1em}.vjs-tree .vjs-tree__content.has-line{border-left:1px dotted hsla(0,0%,80%,.28)!important}.vjs-tree .vjs-tree__brackets{cursor:pointer}.vjs-tree .vjs-tree__brackets:hover{color:#20a0ff}.vjs-tree .vjs-value__boolean,.vjs-tree .vjs-value__null,.vjs-tree .vjs-value__number{color:#a291f5!important}.vjs-tree .vjs-value__string{color:#dacb4d!important} + +/*! + * Bootstrap v4.6.2 (https://getbootstrap.com/) + * Copyright 2011-2022 The Bootstrap Authors + * Copyright 2011-2022 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#494444;--primary:#adadff;--secondary:#494444;--success:#1f9d55;--info:#1c3d5a;--warning:#b08d2f;--danger:#aa2e28;--light:#f8f9fa;--dark:#494444;--breakpoint-xs:0;--breakpoint-sm:2px;--breakpoint-md:8px;--breakpoint-lg:9px;--breakpoint-xl:10px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,:after,:before{box-sizing:border-box}html{-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);font-family:sans-serif;line-height:1.15}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{background-color:#1c1c1c;color:#e2edf4;font-family:Nunito;font-size:.95rem;font-weight:400;line-height:1.5;margin:0;text-align:left}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;margin-top:0}p{margin-bottom:1rem;margin-top:0}abbr[data-original-title],abbr[title]{border-bottom:0;cursor:help;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{font-style:normal;line-height:inherit}address,dl,ol,ul{margin-bottom:1rem}dl,ol,ul{margin-top:0}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{background-color:transparent;color:#adadff;text-decoration:none}a:hover{color:#6161ff;text-decoration:underline}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}pre{-ms-overflow-style:scrollbar;margin-bottom:1rem;margin-top:0;overflow:auto}figure{margin:0 0 1rem}img{border-style:none}img,svg{vertical-align:middle}svg{overflow:hidden}table{border-collapse:collapse}caption{caption-side:bottom;color:#6c757d;padding-bottom:.75rem;padding-top:.75rem;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{border:0;margin:0;min-width:0;padding:0}legend{color:inherit;display:block;font-size:1.5rem;line-height:inherit;margin-bottom:.5rem;max-width:100%;padding:0;white-space:normal;width:100%}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:none;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}output{display:inline-block}summary{cursor:pointer;display:list-item}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-weight:500;line-height:1.2;margin-bottom:.5rem}.h1,h1{font-size:2.375rem}.h2,h2{font-size:1.9rem}.h3,h3{font-size:1.6625rem}.h4,h4{font-size:1.425rem}.h5,h5{font-size:1.1875rem}.h6,h6{font-size:.95rem}.lead{font-size:1.1875rem;font-weight:300}.display-1{font-size:6rem}.display-1,.display-2{font-weight:300;line-height:1.2}.display-2{font-size:5.5rem}.display-3{font-size:4.5rem}.display-3,.display-4{font-weight:300;line-height:1.2}.display-4{font-size:3.5rem}hr{border:0;border-top:1px solid rgba(0,0,0,.1);margin-bottom:1rem;margin-top:1rem}.small,small{font-size:.875em;font-weight:400}.mark,mark{background-color:#fcf8e3;padding:.2em}.list-inline,.list-unstyled{list-style:none;padding-left:0}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{font-size:1.1875rem;margin-bottom:1rem}.blockquote-footer{color:#6c757d;display:block;font-size:.875em}.blockquote-footer:before{content:"— "}.img-fluid,.img-thumbnail{height:auto;max-width:100%}.img-thumbnail{background-color:#1c1c1c;border:1px solid #dee2e6;border-radius:.25rem;padding:.25rem}.figure{display:inline-block}.figure-img{line-height:1;margin-bottom:.5rem}.figure-caption{color:#6c757d;font-size:90%}code{word-wrap:break-word;color:#e83e8c;font-size:87.5%}a>code{color:inherit}kbd{background-color:#212529;border-radius:.2rem;color:#fff;font-size:87.5%;padding:.2rem .4rem}kbd kbd{font-size:100%;font-weight:700;padding:0}pre{color:#212529;display:block;font-size:87.5%}pre code{color:inherit;font-size:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px;width:100%}@media (min-width:2px){.container,.container-sm{max-width:1137px}}@media (min-width:8px){.container,.container-md,.container-sm{max-width:1138px}}@media (min-width:9px){.container,.container-lg,.container-md,.container-sm{max-width:1139px}}@media (min-width:10px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:flex;flex-wrap:wrap;margin-left:-15px;margin-right:-15px}.no-gutters{margin-left:0;margin-right:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-left:0;padding-right:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{padding-left:15px;padding-right:15px;position:relative;width:100%}.col{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-1>*{flex:0 0 100%;max-width:100%}.row-cols-2>*{flex:0 0 50%;max-width:50%}.row-cols-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-4>*{flex:0 0 25%;max-width:25%}.row-cols-5>*{flex:0 0 20%;max-width:20%}.row-cols-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-auto{flex:0 0 auto;max-width:100%;width:auto}.col-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}@media (min-width:2px){.col-sm{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-sm-1>*{flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-sm-auto{flex:0 0 auto;max-width:100%;width:auto}.col-sm-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-sm-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-sm-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-sm-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-sm-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}}@media (min-width:8px){.col-md{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-md-1>*{flex:0 0 100%;max-width:100%}.row-cols-md-2>*{flex:0 0 50%;max-width:50%}.row-cols-md-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-md-4>*{flex:0 0 25%;max-width:25%}.row-cols-md-5>*{flex:0 0 20%;max-width:20%}.row-cols-md-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-md-auto{flex:0 0 auto;max-width:100%;width:auto}.col-md-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-md-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-md-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-md-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-md-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}}@media (min-width:9px){.col-lg{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-lg-1>*{flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-lg-auto{flex:0 0 auto;max-width:100%;width:auto}.col-lg-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-lg-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-lg-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-lg-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-lg-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}}@media (min-width:10px){.col-xl{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-xl-1>*{flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-xl-auto{flex:0 0 auto;max-width:100%;width:auto}.col-xl-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-xl-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-xl-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-xl-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-xl-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}}.table{color:#e2edf4;margin-bottom:1rem;width:100%}.table td,.table th{border-top:1px solid #343434;padding:.75rem;vertical-align:top}.table thead th{border-bottom:2px solid #343434;vertical-align:bottom}.table tbody+tbody{border-top:2px solid #343434}.table-sm td,.table-sm th{padding:.3rem}.table-bordered,.table-bordered td,.table-bordered th{border:1px solid #343434}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:#343434;color:#e2edf4}.table-primary,.table-primary>td,.table-primary>th{background-color:#e8e8ff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#d4d4ff}.table-hover .table-primary:hover,.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#cfcfff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#cccbcb}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#a09e9e}.table-hover .table-secondary:hover,.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#bfbebe}.table-success,.table-success>td,.table-success>th{background-color:#c0e4cf}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8bcca7}.table-hover .table-success:hover,.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#aedcc1}.table-info,.table-info>td,.table-info>th{background-color:#bfc9d1}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#899aa9}.table-hover .table-info:hover,.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#b0bcc6}.table-warning,.table-warning>td,.table-warning>th{background-color:#e9dfc5}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#d6c493}.table-hover .table-warning:hover,.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#e2d5b3}.table-danger,.table-danger>td,.table-danger>th{background-color:#e7c4c3}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#d3928f}.table-hover .table-danger:hover,.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#e0b2b1}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover,.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#cccbcb}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#a09e9e}.table-hover .table-dark:hover,.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#bfbebe}.table-active,.table-active>td,.table-active>th{background-color:#343434}.table-hover .table-active:hover,.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:#272727}.table .thead-dark th{background-color:#494444;border-color:#5d5656;color:#fff}.table .thead-light th{background-color:#e9ecef;border-color:#343434;color:#495057}.table-dark{background-color:#494444;color:#fff}.table-dark td,.table-dark th,.table-dark thead th{border-color:#5d5656}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:hsla(0,0%,100%,.05)}.table-dark.table-hover tbody tr:hover{background-color:hsla(0,0%,100%,.075);color:#fff}@media (max-width:1.98px){.table-responsive-sm{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:7.98px){.table-responsive-md{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-md>.table-bordered{border:0}}@media (max-width:8.98px){.table-responsive-lg{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:9.98px){.table-responsive-xl{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive>.table-bordered{border:0}.form-control{background-clip:padding-box;background-color:#242424;border:1px solid #343434;border-radius:.25rem;color:#e2edf4;display:block;font-size:.95rem;font-weight:400;height:calc(1.5em + .75rem + 2px);line-height:1.5;padding:.375rem .75rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:100%}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{background-color:#242424;border-color:#fff;box-shadow:0 0 0 .2rem rgba(173,173,255,.25);color:#e2edf4;outline:0}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #e2edf4}select.form-control:focus::-ms-value{background-color:#242424;color:#e2edf4}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{font-size:inherit;line-height:1.5;margin-bottom:0;padding-bottom:calc(.375rem + 1px);padding-top:calc(.375rem + 1px)}.col-form-label-lg{font-size:1.1875rem;line-height:1.5;padding-bottom:calc(.5rem + 1px);padding-top:calc(.5rem + 1px)}.col-form-label-sm{font-size:.83125rem;line-height:1.5;padding-bottom:calc(.25rem + 1px);padding-top:calc(.25rem + 1px)}.form-control-plaintext{background-color:transparent;border:solid transparent;border-width:1px 0;color:#e2edf4;display:block;font-size:.95rem;line-height:1.5;margin-bottom:0;padding:.375rem 0;width:100%}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-left:0;padding-right:0}.form-control-sm{border-radius:.2rem;font-size:.83125rem;height:calc(1.5em + .5rem + 2px);line-height:1.5;padding:.25rem .5rem}.form-control-lg{border-radius:.3rem;font-size:1.1875rem;height:calc(1.5em + 1rem + 2px);line-height:1.5;padding:.5rem 1rem}select.form-control[multiple],select.form-control[size],textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-left:-5px;margin-right:-5px}.form-row>.col,.form-row>[class*=col-]{padding-left:5px;padding-right:5px}.form-check{display:block;padding-left:1.25rem;position:relative}.form-check-input{margin-left:-1.25rem;margin-top:.3rem;position:absolute}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{align-items:center;display:inline-flex;margin-right:.75rem;padding-left:0}.form-check-inline .form-check-input{margin-left:0;margin-right:.3125rem;margin-top:0;position:static}.valid-feedback{color:#1f9d55;display:none;font-size:.875em;margin-top:.25rem;width:100%}.valid-tooltip{background-color:rgba(31,157,85,.9);border-radius:.25rem;color:#fff;display:none;font-size:.83125rem;left:0;line-height:1.5;margin-top:.1rem;max-width:100%;padding:.25rem .5rem;position:absolute;top:100%;z-index:5}.form-row>.col>.valid-tooltip,.form-row>[class*=col-]>.valid-tooltip{left:5px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%231f9d55' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);border-color:#1f9d55;padding-right:calc(1.5em + .75rem)!important}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#1f9d55;box-shadow:0 0 0 .2rem rgba(31,157,85,.25)}.was-validated select.form-control:valid,select.form-control.is-valid{background-position:right 1.5rem center;padding-right:3rem!important}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.custom-select.is-valid,.was-validated .custom-select:valid{background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23494444' d='M2 0 0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat,#242424 url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%231f9d55' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat;border-color:#1f9d55;padding-right:calc(.75em + 2.3125rem)!important}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#1f9d55;box-shadow:0 0 0 .2rem rgba(31,157,85,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#1f9d55}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#1f9d55}.custom-control-input.is-valid~.custom-control-label:before,.was-validated .custom-control-input:valid~.custom-control-label:before{border-color:#1f9d55}.custom-control-input.is-valid:checked~.custom-control-label:before,.was-validated .custom-control-input:valid:checked~.custom-control-label:before{background-color:#27c86c;border-color:#27c86c}.custom-control-input.is-valid:focus~.custom-control-label:before,.was-validated .custom-control-input:valid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(31,157,85,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label:before{border-color:#1f9d55}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#1f9d55}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#1f9d55;box-shadow:0 0 0 .2rem rgba(31,157,85,.25)}.invalid-feedback{color:#aa2e28;display:none;font-size:.875em;margin-top:.25rem;width:100%}.invalid-tooltip{background-color:rgba(170,46,40,.9);border-radius:.25rem;color:#fff;display:none;font-size:.83125rem;left:0;line-height:1.5;margin-top:.1rem;max-width:100%;padding:.25rem .5rem;position:absolute;top:100%;z-index:5}.form-row>.col>.invalid-tooltip,.form-row>[class*=col-]>.invalid-tooltip{left:5px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23aa2e28'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23aa2e28' stroke='none'/%3E%3C/svg%3E");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);border-color:#aa2e28;padding-right:calc(1.5em + .75rem)!important}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#aa2e28;box-shadow:0 0 0 .2rem rgba(170,46,40,.25)}.was-validated select.form-control:invalid,select.form-control.is-invalid{background-position:right 1.5rem center;padding-right:3rem!important}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23494444' d='M2 0 0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat,#242424 url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23aa2e28'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23aa2e28' stroke='none'/%3E%3C/svg%3E") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat;border-color:#aa2e28;padding-right:calc(.75em + 2.3125rem)!important}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#aa2e28;box-shadow:0 0 0 .2rem rgba(170,46,40,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#aa2e28}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#aa2e28}.custom-control-input.is-invalid~.custom-control-label:before,.was-validated .custom-control-input:invalid~.custom-control-label:before{border-color:#aa2e28}.custom-control-input.is-invalid:checked~.custom-control-label:before,.was-validated .custom-control-input:invalid:checked~.custom-control-label:before{background-color:#d03d35;border-color:#d03d35}.custom-control-input.is-invalid:focus~.custom-control-label:before,.was-validated .custom-control-input:invalid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(170,46,40,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label:before{border-color:#aa2e28}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#aa2e28}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#aa2e28;box-shadow:0 0 0 .2rem rgba(170,46,40,.25)}.form-inline{align-items:center;display:flex;flex-flow:row wrap}.form-inline .form-check{width:100%}@media (min-width:2px){.form-inline label{justify-content:center}.form-inline .form-group,.form-inline label{align-items:center;display:flex;margin-bottom:0}.form-inline .form-group{flex:0 0 auto;flex-flow:row wrap}.form-inline .form-control{display:inline-block;vertical-align:middle;width:auto}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{align-items:center;display:flex;justify-content:center;padding-left:0;width:auto}.form-inline .form-check-input{flex-shrink:0;margin-left:0;margin-right:.25rem;margin-top:0;position:relative}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;color:#e2edf4;display:inline-block;font-size:.95rem;font-weight:400;line-height:1.5;padding:.375rem .75rem;text-align:center;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#e2edf4;text-decoration:none}.btn.focus,.btn:focus{box-shadow:0 0 0 .2rem rgba(173,173,255,.25);outline:0}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{background-color:#adadff;border-color:#adadff;color:#212529}.btn-primary.focus,.btn-primary:focus,.btn-primary:hover{background-color:#8787ff;border-color:#7a7aff;color:#fff}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(152,153,223,.5)}.btn-primary.disabled,.btn-primary:disabled{background-color:#adadff;border-color:#adadff;color:#212529}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{background-color:#7a7aff;border-color:#6d6dff;color:#fff}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(152,153,223,.5)}.btn-secondary{background-color:#494444;border-color:#494444;color:#fff}.btn-secondary.focus,.btn-secondary:focus,.btn-secondary:hover{background-color:#353232;border-color:#2f2b2b;color:#fff}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem hsla(0,2%,38%,.5)}.btn-secondary.disabled,.btn-secondary:disabled{background-color:#494444;border-color:#494444;color:#fff}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{background-color:#2f2b2b;border-color:#282525;color:#fff}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem hsla(0,2%,38%,.5)}.btn-success{background-color:#1f9d55;border-color:#1f9d55;color:#fff}.btn-success.focus,.btn-success:focus,.btn-success:hover{background-color:#197d44;border-color:#17723e;color:#fff}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(65,172,111,.5)}.btn-success.disabled,.btn-success:disabled{background-color:#1f9d55;border-color:#1f9d55;color:#fff}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{background-color:#17723e;border-color:#146838;color:#fff}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(65,172,111,.5)}.btn-info{background-color:#1c3d5a;border-color:#1c3d5a;color:#fff}.btn-info.focus,.btn-info:focus,.btn-info:hover{background-color:#13293d;border-color:#102333;color:#fff}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(62,90,115,.5)}.btn-info.disabled,.btn-info:disabled{background-color:#1c3d5a;border-color:#1c3d5a;color:#fff}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{background-color:#102333;border-color:#0d1c29;color:#fff}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(62,90,115,.5)}.btn-warning{background-color:#b08d2f;border-color:#b08d2f;color:#fff}.btn-warning.focus,.btn-warning:focus,.btn-warning:hover{background-color:#927527;border-color:#886d24;color:#fff}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(188,158,78,.5)}.btn-warning.disabled,.btn-warning:disabled{background-color:#b08d2f;border-color:#b08d2f;color:#fff}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{background-color:#886d24;border-color:#7e6522;color:#fff}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(188,158,78,.5)}.btn-danger{background-color:#aa2e28;border-color:#aa2e28;color:#fff}.btn-danger.focus,.btn-danger:focus,.btn-danger:hover{background-color:#8b2621;border-color:#81231e;color:#fff}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(183,77,72,.5)}.btn-danger.disabled,.btn-danger:disabled{background-color:#aa2e28;border-color:#aa2e28;color:#fff}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{background-color:#81231e;border-color:#76201c;color:#fff}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(183,77,72,.5)}.btn-light{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-light.focus,.btn-light:focus,.btn-light:hover{background-color:#e2e6ea;border-color:#dae0e5;color:#212529}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem hsla(220,4%,85%,.5)}.btn-light.disabled,.btn-light:disabled{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{background-color:#dae0e5;border-color:#d3d9df;color:#212529}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem hsla(220,4%,85%,.5)}.btn-dark{background-color:#494444;border-color:#494444;color:#fff}.btn-dark.focus,.btn-dark:focus,.btn-dark:hover{background-color:#353232;border-color:#2f2b2b;color:#fff}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem hsla(0,2%,38%,.5)}.btn-dark.disabled,.btn-dark:disabled{background-color:#494444;border-color:#494444;color:#fff}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{background-color:#2f2b2b;border-color:#282525;color:#fff}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem hsla(0,2%,38%,.5)}.btn-outline-primary{border-color:#adadff;color:#adadff}.btn-outline-primary:hover{background-color:#adadff;border-color:#adadff;color:#212529}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(173,173,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{background-color:transparent;color:#adadff}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{background-color:#adadff;border-color:#adadff;color:#212529}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(173,173,255,.5)}.btn-outline-secondary{border-color:#494444;color:#494444}.btn-outline-secondary:hover{background-color:#494444;border-color:#494444;color:#fff}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(73,68,68,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{background-color:transparent;color:#494444}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{background-color:#494444;border-color:#494444;color:#fff}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(73,68,68,.5)}.btn-outline-success{border-color:#1f9d55;color:#1f9d55}.btn-outline-success:hover{background-color:#1f9d55;border-color:#1f9d55;color:#fff}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(31,157,85,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{background-color:transparent;color:#1f9d55}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{background-color:#1f9d55;border-color:#1f9d55;color:#fff}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(31,157,85,.5)}.btn-outline-info{border-color:#1c3d5a;color:#1c3d5a}.btn-outline-info:hover{background-color:#1c3d5a;border-color:#1c3d5a;color:#fff}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(28,61,90,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{background-color:transparent;color:#1c3d5a}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{background-color:#1c3d5a;border-color:#1c3d5a;color:#fff}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(28,61,90,.5)}.btn-outline-warning{border-color:#b08d2f;color:#b08d2f}.btn-outline-warning:hover{background-color:#b08d2f;border-color:#b08d2f;color:#fff}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(176,141,47,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{background-color:transparent;color:#b08d2f}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{background-color:#b08d2f;border-color:#b08d2f;color:#fff}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(176,141,47,.5)}.btn-outline-danger{border-color:#aa2e28;color:#aa2e28}.btn-outline-danger:hover{background-color:#aa2e28;border-color:#aa2e28;color:#fff}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(170,46,40,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{background-color:transparent;color:#aa2e28}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{background-color:#aa2e28;border-color:#aa2e28;color:#fff}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(170,46,40,.5)}.btn-outline-light{border-color:#f8f9fa;color:#f8f9fa}.btn-outline-light:hover{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{background-color:transparent;color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{border-color:#494444;color:#494444}.btn-outline-dark:hover{background-color:#494444;border-color:#494444;color:#fff}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(73,68,68,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{background-color:transparent;color:#494444}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{background-color:#494444;border-color:#494444;color:#fff}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(73,68,68,.5)}.btn-link{color:#adadff;font-weight:400;text-decoration:none}.btn-link:hover{color:#6161ff}.btn-link.focus,.btn-link:focus,.btn-link:hover{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{border-radius:.3rem;font-size:1.1875rem;line-height:1.5;padding:.5rem 1rem}.btn-group-sm>.btn,.btn-sm{border-radius:.2rem;font-size:.83125rem;line-height:1.5;padding:.25rem .5rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;position:relative;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.width{height:auto;transition:width .35s ease;width:0}@media (prefers-reduced-motion:reduce){.collapsing.width{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle:after{border-bottom:0;border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropdown-toggle:empty:after{margin-left:0}.dropdown-menu{background-clip:padding-box;background-color:#181818;border:1px solid rgba(0,0,0,.15);border-radius:.25rem;color:#e2edf4;display:none;float:left;font-size:.95rem;left:0;list-style:none;margin:.125rem 0 0;min-width:10rem;padding:.5rem 0;position:absolute;text-align:left;top:100%;z-index:1000}.dropdown-menu-left{left:0;right:auto}.dropdown-menu-right{left:auto;right:0}@media (min-width:2px){.dropdown-menu-sm-left{left:0;right:auto}.dropdown-menu-sm-right{left:auto;right:0}}@media (min-width:8px){.dropdown-menu-md-left{left:0;right:auto}.dropdown-menu-md-right{left:auto;right:0}}@media (min-width:9px){.dropdown-menu-lg-left{left:0;right:auto}.dropdown-menu-lg-right{left:auto;right:0}}@media (min-width:10px){.dropdown-menu-xl-left{left:0;right:auto}.dropdown-menu-xl-right{left:auto;right:0}}.dropup .dropdown-menu{bottom:100%;margin-bottom:.125rem;margin-top:0;top:auto}.dropup .dropdown-toggle:after{border-bottom:.3em solid;border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:0;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropup .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-menu{left:100%;margin-left:.125rem;margin-top:0;right:auto;top:0}.dropright .dropdown-toggle:after{border-bottom:.3em solid transparent;border-left:.3em solid;border-right:0;border-top:.3em solid transparent;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropright .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-toggle:after{vertical-align:0}.dropleft .dropdown-menu{left:auto;margin-right:.125rem;margin-top:0;right:100%;top:0}.dropleft .dropdown-toggle:after{content:"";display:inline-block;display:none;margin-left:.255em;vertical-align:.255em}.dropleft .dropdown-toggle:before{border-bottom:.3em solid transparent;border-right:.3em solid;border-top:.3em solid transparent;content:"";display:inline-block;margin-right:.255em;vertical-align:.255em}.dropleft .dropdown-toggle:empty:after{margin-left:0}.dropleft .dropdown-toggle:before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{bottom:auto;right:auto}.dropdown-divider{border-top:1px solid #e9ecef;height:0;margin:.5rem 0;overflow:hidden}.dropdown-item{background-color:transparent;border:0;clear:both;color:#fff;display:block;font-weight:400;padding:.25rem 1.5rem;text-align:inherit;white-space:nowrap;width:100%}.dropdown-item:focus,.dropdown-item:hover{background-color:#e9ecef;color:#16181b;text-decoration:none}.dropdown-item.active,.dropdown-item:active{background-color:#adadff;color:#fff;text-decoration:none}.dropdown-item.disabled,.dropdown-item:disabled{background-color:transparent;color:#adb5bd;pointer-events:none}.dropdown-menu.show{display:block}.dropdown-header{color:#6c757d;display:block;font-size:.83125rem;margin-bottom:0;padding:.5rem 1.5rem;white-space:nowrap}.dropdown-item-text{color:#fff;display:block;padding:.25rem 1.5rem}.btn-group,.btn-group-vertical{display:inline-flex;position:relative;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{flex:1 1 auto;position:relative}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.dropdown-toggle-split{padding-left:.5625rem;padding-right:.5625rem}.dropdown-toggle-split:after,.dropright .dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after{margin-left:0}.dropleft .dropdown-toggle-split:before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-left:.375rem;padding-right:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-left:.75rem;padding-right:.75rem}.btn-group-vertical{align-items:flex-start;flex-direction:column;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-left-radius:0;border-bottom-right-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{clip:rect(0,0,0,0);pointer-events:none;position:absolute}.input-group{align-items:stretch;display:flex;flex-wrap:wrap;position:relative;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{flex:1 1 auto;margin-bottom:0;min-width:0;position:relative;width:1%}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.input-group>.custom-file{align-items:center;display:flex}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label:after{border-bottom-right-radius:0;border-top-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-bottom-left-radius:0;border-top-left-radius:0}.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label,.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label:after,.input-group.has-validation>.custom-select:nth-last-child(n+3),.input-group.has-validation>.form-control:nth-last-child(n+3),.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label,.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label:after,.input-group:not(.has-validation)>.custom-select:not(:last-child),.input-group:not(.has-validation)>.form-control:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group-append,.input-group-prepend{display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{align-items:center;background-color:#e9ecef;border:1px solid #343434;border-radius:.25rem;color:#e2edf4;display:flex;font-size:.95rem;font-weight:400;line-height:1.5;margin-bottom:0;padding:.375rem .75rem;text-align:center;white-space:nowrap}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{border-radius:.3rem;font-size:1.1875rem;line-height:1.5;padding:.5rem 1rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{border-radius:.2rem;font-size:.83125rem;line-height:1.5;padding:.25rem .5rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.btn,.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.input-group-text,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.btn,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-bottom-right-radius:0;border-top-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-bottom-left-radius:0;border-top-left-radius:0}.custom-control{display:block;min-height:1.425rem;padding-left:1.5rem;position:relative;-webkit-print-color-adjust:exact;print-color-adjust:exact;z-index:1}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{height:1.2125rem;left:0;opacity:0;position:absolute;width:1rem;z-index:-1}.custom-control-input:checked~.custom-control-label:before{background-color:#adadff;border-color:#adadff;color:#fff}.custom-control-input:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(173,173,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label:before{border-color:#fff}.custom-control-input:not(:disabled):active~.custom-control-label:before{background-color:#fff;border-color:#fff;color:#fff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label:before,.custom-control-input[disabled]~.custom-control-label:before{background-color:#e9ecef}.custom-control-label{margin-bottom:0;position:relative;vertical-align:top}.custom-control-label:before{background-color:#242424;border:1px solid #adb5bd;pointer-events:none}.custom-control-label:after,.custom-control-label:before{content:"";display:block;height:1rem;left:-1.5rem;position:absolute;top:.2125rem;width:1rem}.custom-control-label:after{background:50%/50% 50% no-repeat}.custom-checkbox .custom-control-label:before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%23fff' d='m6.564.75-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:before{background-color:#adadff;border-color:#adadff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(173,173,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label:before{background-color:rgba(173,173,255,.5)}.custom-radio .custom-control-label:before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(173,173,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label:before{border-radius:.5rem;left:-2.25rem;pointer-events:all;width:1.75rem}.custom-switch .custom-control-label:after{background-color:#adb5bd;border-radius:.5rem;height:calc(1rem - 4px);left:calc(-2.25rem + 2px);top:calc(.2125rem + 2px);transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:calc(1rem - 4px)}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label:after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label:after{background-color:#242424;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(173,173,255,.5)}.custom-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#242424 url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23494444' d='M2 0 0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat;border:1px solid #343434;border-radius:.25rem;color:#e2edf4;display:inline-block;font-size:.95rem;font-weight:400;height:calc(1.5em + .75rem + 2px);line-height:1.5;padding:.375rem 1.75rem .375rem .75rem;vertical-align:middle;width:100%}.custom-select:focus{border-color:#fff;box-shadow:0 0 0 .2rem rgba(173,173,255,.25);outline:0}.custom-select:focus::-ms-value{background-color:#242424;color:#e2edf4}.custom-select[multiple],.custom-select[size]:not([size="1"]){background-image:none;height:auto;padding-right:.75rem}.custom-select:disabled{background-color:#e9ecef;color:#6c757d}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #e2edf4}.custom-select-sm{font-size:.83125rem;height:calc(1.5em + .5rem + 2px);padding-bottom:.25rem;padding-left:.5rem;padding-top:.25rem}.custom-select-lg{font-size:1.1875rem;height:calc(1.5em + 1rem + 2px);padding-bottom:.5rem;padding-left:1rem;padding-top:.5rem}.custom-file{display:inline-block;margin-bottom:0}.custom-file,.custom-file-input{height:calc(1.5em + .75rem + 2px);position:relative;width:100%}.custom-file-input{margin:0;opacity:0;overflow:hidden;z-index:2}.custom-file-input:focus~.custom-file-label{border-color:#fff;box-shadow:0 0 0 .2rem rgba(173,173,255,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label:after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]:after{content:attr(data-browse)}.custom-file-label{background-color:#242424;border:1px solid #343434;border-radius:.25rem;font-weight:400;height:calc(1.5em + .75rem + 2px);left:0;overflow:hidden;z-index:1}.custom-file-label,.custom-file-label:after{color:#e2edf4;line-height:1.5;padding:.375rem .75rem;position:absolute;right:0;top:0}.custom-file-label:after{background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0;bottom:0;content:"Browse";display:block;height:calc(1.5em + .75rem);z-index:3}.custom-range{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;height:1.4rem;padding:0;width:100%}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #1c1c1c,0 0 0 .2rem rgba(173,173,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #1c1c1c,0 0 0 .2rem rgba(173,173,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #1c1c1c,0 0 0 .2rem rgba(173,173,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;background-color:#adadff;border:0;border-radius:1rem;height:1rem;margin-top:-.25rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#fff}.custom-range::-webkit-slider-runnable-track{background-color:#dee2e6;border-color:transparent;border-radius:1rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.custom-range::-moz-range-thumb{-moz-appearance:none;appearance:none;background-color:#adadff;border:0;border-radius:1rem;height:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#fff}.custom-range::-moz-range-track{background-color:#dee2e6;border-color:transparent;border-radius:1rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.custom-range::-ms-thumb{appearance:none;background-color:#adadff;border:0;border-radius:1rem;height:1rem;margin-left:.2rem;margin-right:.2rem;margin-top:0;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#fff}.custom-range::-ms-track{background-color:transparent;border-color:transparent;border-width:.5rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.custom-range::-ms-fill-lower,.custom-range::-ms-fill-upper{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label:before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label:before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;list-style:none;margin-bottom:0;padding-left:0}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;cursor:default;pointer-events:none}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{background-color:transparent;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem;margin-bottom:-1px}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{background-color:transparent;border-color:transparent;color:#6c757d}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{background-color:#1c1c1c;border-color:#dee2e6 #dee2e6 #1c1c1c;color:#495057}.nav-tabs .dropdown-menu{border-top-left-radius:0;border-top-right-radius:0;margin-top:-1px}.nav-pills .nav-link{background:none;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{background-color:#adadff;color:#fff}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{padding:.5rem 1rem;position:relative}.navbar,.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{align-items:center;display:flex;flex-wrap:wrap;justify-content:space-between}.navbar-brand{display:inline-block;font-size:1.1875rem;line-height:inherit;margin-right:1rem;padding-bottom:.321875rem;padding-top:.321875rem;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;list-style:none;margin-bottom:0;padding-left:0}.navbar-nav .nav-link{padding-left:0;padding-right:0}.navbar-nav .dropdown-menu{float:none;position:static}.navbar-text{display:inline-block;padding-bottom:.5rem;padding-top:.5rem}.navbar-collapse{align-items:center;flex-basis:100%;flex-grow:1}.navbar-toggler{background-color:transparent;border:1px solid transparent;border-radius:.25rem;font-size:1.1875rem;line-height:1;padding:.25rem .75rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{background:50%/100% 100% no-repeat;content:"";display:inline-block;height:1.5em;vertical-align:middle;width:1.5em}.navbar-nav-scroll{max-height:75vh;overflow-y:auto}@media (max-width:1.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-left:0;padding-right:0}}@media (min-width:2px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{flex-wrap:nowrap}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:7.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-left:0;padding-right:0}}@media (min-width:8px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{flex-wrap:nowrap}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:8.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-left:0;padding-right:0}}@media (min-width:9px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{flex-wrap:nowrap}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:9.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-left:0;padding-right:0}}@media (min-width:10px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{flex-wrap:nowrap}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-left:0;padding-right:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{flex-wrap:nowrap}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand,.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{border-color:rgba(0,0,0,.1);color:rgba(0,0,0,.5)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand,.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:hsla(0,0%,100%,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:hsla(0,0%,100%,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{border-color:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{word-wrap:break-word;background-clip:border-box;background-color:#120f12;border:1px solid rgba(0,0,0,.125);border-radius:.25rem;display:flex;flex-direction:column;min-width:0;position:relative}.card>hr{margin-left:0;margin-right:0}.card>.list-group{border-bottom:inherit;border-top:inherit}.card>.list-group:first-child{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px);border-top-width:0}.card>.list-group:last-child{border-bottom-left-radius:calc(.25rem - 1px);border-bottom-right-radius:calc(.25rem - 1px);border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem}.card-subtitle,.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{background-color:#120f12;border-bottom:1px solid rgba(0,0,0,.125);margin-bottom:0;padding:.75rem 1.25rem}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{background-color:#120f12;border-top:1px solid rgba(0,0,0,.125);padding:.75rem 1.25rem}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{border-bottom:0;margin-bottom:-.75rem}.card-header-pills,.card-header-tabs{margin-left:-.625rem;margin-right:-.625rem}.card-img-overlay{border-radius:calc(.25rem - 1px);bottom:0;left:0;padding:1.25rem;position:absolute;right:0;top:0}.card-img,.card-img-bottom,.card-img-top{flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-left-radius:calc(.25rem - 1px);border-bottom-right-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:2px){.card-deck{display:flex;flex-flow:row wrap;margin-left:-15px;margin-right:-15px}.card-deck .card{flex:1 0 0%;margin-bottom:0;margin-left:15px;margin-right:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:2px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{border-left:0;margin-left:0}.card-group>.card:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:2px){.card-columns{-moz-column-count:3;column-count:3;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-left-radius:0;border-bottom-right-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{background-color:#e9ecef;border-radius:.25rem;display:flex;flex-wrap:wrap;list-style:none;margin-bottom:1rem;padding:.75rem 1rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item:before{color:#6c757d;content:"/";float:left;padding-right:.5rem}.breadcrumb-item+.breadcrumb-item:hover:before{text-decoration:underline;text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{border-radius:.25rem;display:flex;list-style:none;padding-left:0}.page-link{background-color:#fff;border:1px solid #dee2e6;color:#adadff;display:block;line-height:1.25;margin-left:-1px;padding:.5rem .75rem;position:relative}.page-link:hover{background-color:#e9ecef;border-color:#dee2e6;color:#6161ff;text-decoration:none;z-index:2}.page-link:focus{box-shadow:0 0 0 .2rem rgba(173,173,255,.25);outline:0;z-index:3}.page-item:first-child .page-link{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem;margin-left:0}.page-item:last-child .page-link{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.page-item.active .page-link{background-color:#adadff;border-color:#adadff;color:#fff;z-index:3}.page-item.disabled .page-link{background-color:#fff;border-color:#dee2e6;color:#6c757d;cursor:auto;pointer-events:none}.pagination-lg .page-link{font-size:1.1875rem;line-height:1.5;padding:.75rem 1.5rem}.pagination-lg .page-item:first-child .page-link{border-bottom-left-radius:.3rem;border-top-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-bottom-right-radius:.3rem;border-top-right-radius:.3rem}.pagination-sm .page-link{font-size:.83125rem;line-height:1.5;padding:.25rem .5rem}.pagination-sm .page-item:first-child .page-link{border-bottom-left-radius:.2rem;border-top-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-bottom-right-radius:.2rem;border-top-right-radius:.2rem}.badge{border-radius:.25rem;display:inline-block;font-size:.95rem;font-weight:700;line-height:1;padding:.25em .4em;text-align:center;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:baseline;white-space:nowrap}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{border-radius:10rem;padding-left:.6em;padding-right:.6em}.badge-primary{background-color:#adadff;color:#212529}a.badge-primary:focus,a.badge-primary:hover{background-color:#7a7aff;color:#212529}a.badge-primary.focus,a.badge-primary:focus{box-shadow:0 0 0 .2rem rgba(173,173,255,.5);outline:0}.badge-secondary{background-color:#494444;color:#fff}a.badge-secondary:focus,a.badge-secondary:hover{background-color:#2f2b2b;color:#fff}a.badge-secondary.focus,a.badge-secondary:focus{box-shadow:0 0 0 .2rem rgba(73,68,68,.5);outline:0}.badge-success{background-color:#1f9d55;color:#fff}a.badge-success:focus,a.badge-success:hover{background-color:#17723e;color:#fff}a.badge-success.focus,a.badge-success:focus{box-shadow:0 0 0 .2rem rgba(31,157,85,.5);outline:0}.badge-info{background-color:#1c3d5a;color:#fff}a.badge-info:focus,a.badge-info:hover{background-color:#102333;color:#fff}a.badge-info.focus,a.badge-info:focus{box-shadow:0 0 0 .2rem rgba(28,61,90,.5);outline:0}.badge-warning{background-color:#b08d2f;color:#fff}a.badge-warning:focus,a.badge-warning:hover{background-color:#886d24;color:#fff}a.badge-warning.focus,a.badge-warning:focus{box-shadow:0 0 0 .2rem rgba(176,141,47,.5);outline:0}.badge-danger{background-color:#aa2e28;color:#fff}a.badge-danger:focus,a.badge-danger:hover{background-color:#81231e;color:#fff}a.badge-danger.focus,a.badge-danger:focus{box-shadow:0 0 0 .2rem rgba(170,46,40,.5);outline:0}.badge-light{background-color:#f8f9fa;color:#212529}a.badge-light:focus,a.badge-light:hover{background-color:#dae0e5;color:#212529}a.badge-light.focus,a.badge-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5);outline:0}.badge-dark{background-color:#494444;color:#fff}a.badge-dark:focus,a.badge-dark:hover{background-color:#2f2b2b;color:#fff}a.badge-dark.focus,a.badge-dark:focus{box-shadow:0 0 0 .2rem rgba(73,68,68,.5);outline:0}.jumbotron{background-color:#e9ecef;border-radius:.3rem;margin-bottom:2rem;padding:2rem 1rem}@media (min-width:2px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{border-radius:0;padding-left:0;padding-right:0}.alert{border:1px solid transparent;border-radius:.25rem;margin-bottom:1rem;padding:.75rem 1.25rem;position:relative}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3.925rem}.alert-dismissible .close{color:inherit;padding:.75rem 1.25rem;position:absolute;right:0;top:0;z-index:2}.alert-primary{background-color:#efefff;border-color:#e8e8ff;color:#5a5a85}.alert-primary hr{border-top-color:#cfcfff}.alert-primary .alert-link{color:#454567}.alert-secondary{background-color:#dbdada;border-color:#cccbcb;color:#262323}.alert-secondary hr{border-top-color:#bfbebe}.alert-secondary .alert-link{color:#0b0b0b}.alert-success{background-color:#d2ebdd;border-color:#c0e4cf;color:#10522c}.alert-success hr{border-top-color:#aedcc1}.alert-success .alert-link{color:#082715}.alert-info{background-color:#d2d8de;border-color:#bfc9d1;color:#0f202f}.alert-info hr{border-top-color:#b0bcc6}.alert-info .alert-link{color:#030608}.alert-warning{background-color:#efe8d5;border-color:#e9dfc5;color:#5c4918}.alert-warning hr{border-top-color:#e2d5b3}.alert-warning .alert-link{color:#34290d}.alert-danger{background-color:#eed5d4;border-color:#e7c4c3;color:#581815}.alert-danger hr{border-top-color:#e0b2b1}.alert-danger .alert-link{color:#2f0d0b}.alert-light{background-color:#fefefe;border-color:#fdfdfe;color:#818182}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{background-color:#dbdada;border-color:#cccbcb;color:#262323}.alert-dark hr{border-top-color:#bfbebe}.alert-dark .alert-link{color:#0b0b0b}@keyframes progress-bar-stripes{0%{background-position:1rem 0}to{background-position:0 0}}.progress{background-color:#e9ecef;border-radius:.25rem;font-size:.7125rem;height:1rem;line-height:0}.progress,.progress-bar{display:flex;overflow:hidden}.progress-bar{background-color:#adadff;color:#fff;flex-direction:column;justify-content:center;text-align:center;transition:width .6s ease;white-space:nowrap}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-size:1rem 1rem}.progress-bar-animated{animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.media{align-items:flex-start;display:flex}.media-body{flex:1}.list-group{border-radius:.25rem;display:flex;flex-direction:column;margin-bottom:0;padding-left:0}.list-group-item-action{color:#495057;text-align:inherit;width:100%}.list-group-item-action:focus,.list-group-item-action:hover{background-color:#f8f9fa;color:#495057;text-decoration:none;z-index:1}.list-group-item-action:active{background-color:#e9ecef;color:#e2edf4}.list-group-item{background-color:#fff;border:1px solid rgba(0,0,0,.125);display:block;padding:.75rem 1.25rem;position:relative}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{background-color:#fff;color:#6c757d;pointer-events:none}.list-group-item.active{background-color:#adadff;border-color:#adadff;color:#fff;z-index:2}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{border-top-width:1px;margin-top:-1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}@media (min-width:2px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}@media (min-width:8px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-md>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}@media (min-width:9px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}@media (min-width:10px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{background-color:#e8e8ff;color:#5a5a85}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{background-color:#cfcfff;color:#5a5a85}.list-group-item-primary.list-group-item-action.active{background-color:#5a5a85;border-color:#5a5a85;color:#fff}.list-group-item-secondary{background-color:#cccbcb;color:#262323}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{background-color:#bfbebe;color:#262323}.list-group-item-secondary.list-group-item-action.active{background-color:#262323;border-color:#262323;color:#fff}.list-group-item-success{background-color:#c0e4cf;color:#10522c}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{background-color:#aedcc1;color:#10522c}.list-group-item-success.list-group-item-action.active{background-color:#10522c;border-color:#10522c;color:#fff}.list-group-item-info{background-color:#bfc9d1;color:#0f202f}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{background-color:#b0bcc6;color:#0f202f}.list-group-item-info.list-group-item-action.active{background-color:#0f202f;border-color:#0f202f;color:#fff}.list-group-item-warning{background-color:#e9dfc5;color:#5c4918}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{background-color:#e2d5b3;color:#5c4918}.list-group-item-warning.list-group-item-action.active{background-color:#5c4918;border-color:#5c4918;color:#fff}.list-group-item-danger{background-color:#e7c4c3;color:#581815}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{background-color:#e0b2b1;color:#581815}.list-group-item-danger.list-group-item-action.active{background-color:#581815;border-color:#581815;color:#fff}.list-group-item-light{background-color:#fdfdfe;color:#818182}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{background-color:#ececf6;color:#818182}.list-group-item-light.list-group-item-action.active{background-color:#818182;border-color:#818182;color:#fff}.list-group-item-dark{background-color:#cccbcb;color:#262323}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{background-color:#bfbebe;color:#262323}.list-group-item-dark.list-group-item-action.active{background-color:#262323;border-color:#262323;color:#fff}.close{color:#000;float:right;font-size:1.425rem;font-weight:700;line-height:1;opacity:.5;text-shadow:0 1px 0 #fff}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{background-color:transparent;border:0;padding:0}a.close.disabled{pointer-events:none}.toast{background-clip:padding-box;background-color:hsla(0,0%,100%,.85);border:1px solid rgba(0,0,0,.1);border-radius:.25rem;box-shadow:0 .25rem .75rem rgba(0,0,0,.1);flex-basis:350px;font-size:.875rem;max-width:350px;opacity:0}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{align-items:center;background-clip:padding-box;background-color:hsla(0,0%,100%,.85);border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px);color:#6c757d;display:flex;padding:.25rem .75rem}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{display:none;height:100%;left:0;outline:0;overflow:hidden;position:fixed;top:0;width:100%;z-index:1050}.modal-dialog{margin:.5rem;pointer-events:none;position:relative;width:auto}.modal.fade .modal-dialog{transform:translateY(-50px);transition:transform .3s ease-out}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{align-items:center;display:flex;min-height:calc(100% - 1rem)}.modal-dialog-centered:before{content:"";display:block;height:calc(100vh - 1rem);height:-moz-min-content;height:min-content}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;height:100%;justify-content:center}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable:before{content:none}.modal-content{background-clip:padding-box;background-color:#181818;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;display:flex;flex-direction:column;outline:0;pointer-events:auto;position:relative;width:100%}.modal-backdrop{background-color:#7e7e7e;height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:1040}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{align-items:flex-start;border-bottom:1px solid #343434;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px);display:flex;justify-content:space-between;padding:1rem}.modal-header .close{margin:-1rem -1rem -1rem auto;padding:1rem}.modal-title{line-height:1.5;margin-bottom:0}.modal-body{flex:1 1 auto;padding:1rem;position:relative}.modal-footer{align-items:center;border-bottom-left-radius:calc(.3rem - 1px);border-bottom-right-radius:calc(.3rem - 1px);border-top:1px solid #343434;display:flex;flex-wrap:wrap;justify-content:flex-end;padding:.75rem}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{height:50px;overflow:scroll;position:absolute;top:-9999px;width:50px}@media (min-width:2px){.modal-dialog{margin:1.75rem auto;max-width:500px}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered:before{height:calc(100vh - 3.5rem);height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:9px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:10px){.modal-xl{max-width:1140px}}.tooltip{word-wrap:break-word;display:block;font-family:Nunito;font-size:.83125rem;font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;line-height:1.5;margin:0;opacity:0;position:absolute;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;z-index:1070}.tooltip.show{opacity:.9}.tooltip .arrow{display:block;height:.4rem;position:absolute;width:.8rem}.tooltip .arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow:before,.bs-tooltip-top .arrow:before{border-top-color:#000;border-width:.4rem .4rem 0;top:0}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{height:.8rem;left:0;width:.4rem}.bs-tooltip-auto[x-placement^=right] .arrow:before,.bs-tooltip-right .arrow:before{border-right-color:#000;border-width:.4rem .4rem .4rem 0;right:0}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow:before,.bs-tooltip-bottom .arrow:before{border-bottom-color:#000;border-width:0 .4rem .4rem;bottom:0}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{height:.8rem;right:0;width:.4rem}.bs-tooltip-auto[x-placement^=left] .arrow:before,.bs-tooltip-left .arrow:before{border-left-color:#000;border-width:.4rem 0 .4rem .4rem;left:0}.tooltip-inner{background-color:#000;border-radius:.25rem;color:#fff;max-width:200px;padding:.25rem .5rem;text-align:center}.popover{word-wrap:break-word;background-clip:padding-box;background-color:#fff;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;font-family:Nunito;font-size:.83125rem;font-style:normal;font-weight:400;left:0;letter-spacing:normal;line-break:auto;line-height:1.5;max-width:276px;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;top:0;white-space:normal;word-break:normal;word-spacing:normal;z-index:1060}.popover,.popover .arrow{display:block;position:absolute}.popover .arrow{height:.5rem;margin:0 .3rem;width:1rem}.popover .arrow:after,.popover .arrow:before{border-color:transparent;border-style:solid;content:"";display:block;position:absolute}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow:before,.bs-popover-top>.arrow:before{border-top-color:rgba(0,0,0,.25);border-width:.5rem .5rem 0;bottom:0}.bs-popover-auto[x-placement^=top]>.arrow:after,.bs-popover-top>.arrow:after{border-top-color:#fff;border-width:.5rem .5rem 0;bottom:1px}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{height:1rem;left:calc(-.5rem - 1px);margin:.3rem 0;width:.5rem}.bs-popover-auto[x-placement^=right]>.arrow:before,.bs-popover-right>.arrow:before{border-right-color:rgba(0,0,0,.25);border-width:.5rem .5rem .5rem 0;left:0}.bs-popover-auto[x-placement^=right]>.arrow:after,.bs-popover-right>.arrow:after{border-right-color:#fff;border-width:.5rem .5rem .5rem 0;left:1px}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow:before,.bs-popover-bottom>.arrow:before{border-bottom-color:rgba(0,0,0,.25);border-width:0 .5rem .5rem;top:0}.bs-popover-auto[x-placement^=bottom]>.arrow:after,.bs-popover-bottom>.arrow:after{border-bottom-color:#fff;border-width:0 .5rem .5rem;top:1px}.bs-popover-auto[x-placement^=bottom] .popover-header:before,.bs-popover-bottom .popover-header:before{border-bottom:1px solid #f7f7f7;content:"";display:block;left:50%;margin-left:-.5rem;position:absolute;top:0;width:1rem}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{height:1rem;margin:.3rem 0;right:calc(-.5rem - 1px);width:.5rem}.bs-popover-auto[x-placement^=left]>.arrow:before,.bs-popover-left>.arrow:before{border-left-color:rgba(0,0,0,.25);border-width:.5rem 0 .5rem .5rem;right:0}.bs-popover-auto[x-placement^=left]>.arrow:after,.bs-popover-left>.arrow:after{border-left-color:#fff;border-width:.5rem 0 .5rem .5rem;right:1px}.popover-header{background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px);font-size:.95rem;margin-bottom:0;padding:.5rem .75rem}.popover-header:empty{display:none}.popover-body{color:#e2edf4;padding:.5rem .75rem}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{overflow:hidden;position:relative;width:100%}.carousel-inner:after{clear:both;content:"";display:block}.carousel-item{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:none;float:left;margin-right:-100%;position:relative;transition:transform .6s ease-in-out;width:100%}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transform:none;transition-property:opacity}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{opacity:1;z-index:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{opacity:0;transition:opacity 0s .6s;z-index:0}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{align-items:center;background:none;border:0;bottom:0;color:#fff;display:flex;justify-content:center;opacity:.5;padding:0;position:absolute;text-align:center;top:0;transition:opacity .15s ease;width:15%;z-index:1}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;opacity:.9;outline:0;text-decoration:none}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{background:50%/100% 100% no-repeat;display:inline-block;height:20px;width:20px}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='m5.25 0-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='m2.75 0-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{bottom:0;display:flex;justify-content:center;left:0;list-style:none;margin-left:15%;margin-right:15%;padding-left:0;position:absolute;right:0;z-index:15}.carousel-indicators li{background-clip:padding-box;background-color:#fff;border-bottom:10px solid transparent;border-top:10px solid transparent;box-sizing:content-box;cursor:pointer;flex:0 1 auto;height:3px;margin-left:3px;margin-right:3px;opacity:.5;text-indent:-999px;transition:opacity .6s ease;width:30px}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{bottom:20px;color:#fff;left:15%;padding-bottom:20px;padding-top:20px;position:absolute;right:15%;text-align:center;z-index:10}@keyframes spinner-border{to{transform:rotate(1turn)}}.spinner-border{animation:spinner-border .75s linear infinite;border:.25em solid;border-radius:50%;border-right:.25em solid transparent;display:inline-block;height:2rem;vertical-align:-.125em;width:2rem}.spinner-border-sm{border-width:.2em;height:1rem;width:1rem}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{animation:spinner-grow .75s linear infinite;background-color:currentcolor;border-radius:50%;display:inline-block;height:2rem;opacity:0;vertical-align:-.125em;width:2rem}.spinner-grow-sm{height:1rem;width:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{animation-duration:1.5s}}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#adadff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#7a7aff!important}.bg-secondary{background-color:#494444!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#2f2b2b!important}.bg-success{background-color:#1f9d55!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#17723e!important}.bg-info{background-color:#1c3d5a!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#102333!important}.bg-warning{background-color:#b08d2f!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#886d24!important}.bg-danger{background-color:#aa2e28!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#81231e!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#494444!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#2f2b2b!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #303030!important}.border-top{border-top:1px solid #303030!important}.border-right{border-right:1px solid #303030!important}.border-bottom{border-bottom:1px solid #303030!important}.border-left{border-left:1px solid #303030!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#adadff!important}.border-secondary{border-color:#494444!important}.border-success{border-color:#1f9d55!important}.border-info{border-color:#1c3d5a!important}.border-warning{border-color:#b08d2f!important}.border-danger{border-color:#aa2e28!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#494444!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important}.rounded-right,.rounded-top{border-top-right-radius:.25rem!important}.rounded-bottom,.rounded-right{border-bottom-right-radius:.25rem!important}.rounded-bottom,.rounded-left{border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix:after{clear:both;content:"";display:block}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media (min-width:2px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media (min-width:8px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media (min-width:9px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media (min-width:10px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.embed-responsive:before{content:"";display:block}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{border:0;bottom:0;height:100%;left:0;position:absolute;top:0;width:100%}.embed-responsive-21by9:before{padding-top:42.85714286%}.embed-responsive-16by9:before{padding-top:56.25%}.embed-responsive-4by3:before{padding-top:75%}.embed-responsive-1by1:before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media (min-width:2px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media (min-width:8px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media (min-width:9px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media (min-width:10px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:2px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:8px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:9px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:10px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.fixed-top{top:0}.fixed-bottom,.fixed-top{left:0;position:fixed;right:0;z-index:1030}.fixed-bottom{bottom:0}@supports (position:sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{clip:rect(0,0,0,0);border:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;overflow:visible;position:static;white-space:normal;width:auto}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:2px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:8px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:9px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:10px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link:after{background-color:transparent;bottom:0;content:"";left:0;pointer-events:auto;position:absolute;right:0;top:0;z-index:1}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:2px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:8px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:9px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:10px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#adadff!important}a.text-primary:focus,a.text-primary:hover{color:#6161ff!important}.text-secondary{color:#494444!important}a.text-secondary:focus,a.text-secondary:hover{color:#211f1f!important}.text-success{color:#1f9d55!important}a.text-success:focus,a.text-success:hover{color:#125d32!important}.text-info{color:#1c3d5a!important}a.text-info:focus,a.text-info:hover{color:#0a1520!important}.text-warning{color:#b08d2f!important}a.text-warning:focus,a.text-warning:hover{color:#745d1f!important}.text-danger{color:#aa2e28!important}a.text-danger:focus,a.text-danger:hover{color:#6c1d19!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#494444!important}a.text-dark:focus,a.text-dark:hover{color:#211f1f!important}.text-body{color:#e2edf4!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:hsla(0,0%,100%,.5)!important}.text-hide{background-color:transparent;border:0;color:transparent;font:0/0 a;text-shadow:none}.text-decoration-none{text-decoration:none!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,:after,:before{box-shadow:none!important;text-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]:after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd}blockquote,img,pre,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}.container,body{min-width:9px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#343434}.table .thead-dark th{border-color:#343434;color:inherit}}body{padding-bottom:20px}.container{width:1140px}html{min-width:1140px}[v-cloak]{display:none}svg.icon{height:1rem;width:1rem}.header{border-bottom:1px solid #343434}.header svg.logo{height:2rem;width:2rem}.sidebar .nav-item a{color:#6e6b6b;padding:.5rem 0}.sidebar .nav-item a svg{fill:#9f9898;height:1rem;margin-right:15px;width:1rem}.sidebar .nav-item a.active{color:#adadff}.sidebar .nav-item a.active svg{fill:#adadff}.card{border:none;box-shadow:0 2px 3px #1c1c1c}.card .bottom-radius{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card .card-header{background-color:#120f12;border-bottom:none;padding-bottom:.7rem;padding-top:.7rem}.card .card-header .btn-group .btn{padding:.2rem .5rem}.card .card-header h5{margin:0}.card .table td,.card .table th{padding:.75rem 1.25rem}.card .table.table-sm td,.card .table.table-sm th{padding:1rem 1.25rem}.card .table th{background-color:#181818;border-bottom:0;font-weight:400;padding:.5rem 1.25rem}.card .table:not(.table-borderless) td{border-top:1px solid #343434}.card .table.penultimate-column-right td:nth-last-child(2),.card .table.penultimate-column-right th:nth-last-child(2){text-align:right}.card .table td.table-fit,.card .table th.table-fit{white-space:nowrap;width:1%}.fill-text-color{fill:#e2edf4}.fill-danger{fill:#aa2e28}.fill-warning{fill:#b08d2f}.fill-info{fill:#1c3d5a}.fill-success{fill:#1f9d55}.fill-primary{fill:#adadff}button:hover .fill-primary{fill:#fff}.btn-outline-primary.active .fill-primary{fill:#1c1c1c}.btn-outline-primary:not(:disabled):not(.disabled).active:focus{box-shadow:none!important}.control-action svg{fill:#ccd2df;height:1.2rem;width:1.2rem}.control-action svg:hover{fill:#adadff}.info-icon{fill:#ccd2df}.paginator .btn{color:#9ea7ac;text-decoration:none}.paginator .btn:hover{color:#adadff}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.spin{animation:spin 2s linear infinite}.card .nav-pills .nav-link.active{background:none;border-bottom:2px solid #adadff;color:#adadff}.card .nav-pills .nav-link{border-radius:0;color:#e2edf4;font-size:.9rem;padding:.75rem 1.25rem}.list-enter-active:not(.dontanimate){transition:background 1s linear}.list-enter:not(.dontanimate),.list-leave-to:not(.dontanimate){background:#505e4a}.card table td{vertical-align:middle!important}.card-bg-secondary,.code-bg{background:#262525}.disabled-watcher{background:#aa2e28;color:#fff;padding:.75rem}.badge-sm{font-size:.75rem} diff --git a/public/vendor/horizon/app.css b/public/vendor/horizon/app.css new file mode 100644 index 00000000..b5224fdf --- /dev/null +++ b/public/vendor/horizon/app.css @@ -0,0 +1,8 @@ +@charset "UTF-8";.vjs-tree{font-family:Monaco,Menlo,Consolas,Bitstream Vera Sans Mono,monospace!important}.vjs-tree.is-root{position:relative}.vjs-tree .vjs-tree__content{padding-left:1em}.vjs-tree .vjs-tree__content.has-line{border-left:1px dotted hsla(0,0%,80%,.28)!important}.vjs-tree .vjs-tree__brackets{cursor:pointer}.vjs-tree .vjs-tree__brackets:hover{color:#20a0ff}.vjs-tree .vjs-value__boolean,.vjs-tree .vjs-value__null,.vjs-tree .vjs-value__number{color:#a291f5!important}.vjs-tree .vjs-value__string{color:#dacb4d!important} + +/*! + * Bootstrap v4.6.2 (https://getbootstrap.com/) + * Copyright 2011-2022 The Bootstrap Authors + * Copyright 2011-2022 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#7746ec;--secondary:#dae1e7;--success:#51d88a;--info:#bcdefa;--warning:#ffa260;--danger:#ef5753;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:2px;--breakpoint-md:8px;--breakpoint-lg:9px;--breakpoint-xl:10px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,:after,:before{box-sizing:border-box}html{-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:rgba(0,0,0,0);font-family:sans-serif;line-height:1.15}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{background-color:#ebebeb;color:#212529;font-family:Nunito,sans-serif;font-size:.95rem;font-weight:400;line-height:1.5;margin:0;text-align:left}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;margin-top:0}p{margin-bottom:1rem;margin-top:0}abbr[data-original-title],abbr[title]{border-bottom:0;cursor:help;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{font-style:normal;line-height:inherit}address,dl,ol,ul{margin-bottom:1rem}dl,ol,ul{margin-top:0}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{background-color:transparent;color:#7746ec;text-decoration:none}a:hover{color:#4d15d0;text-decoration:underline}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}pre{-ms-overflow-style:scrollbar;margin-bottom:1rem;margin-top:0;overflow:auto}figure{margin:0 0 1rem}img{border-style:none}img,svg{vertical-align:middle}svg{overflow:hidden}table{border-collapse:collapse}caption{caption-side:bottom;color:#6c757d;padding-bottom:.75rem;padding-top:.75rem;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit;margin:0}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{border:0;margin:0;min-width:0;padding:0}legend{color:inherit;display:block;font-size:1.5rem;line-height:inherit;margin-bottom:.5rem;max-width:100%;padding:0;white-space:normal;width:100%}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:none;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}output{display:inline-block}summary{cursor:pointer;display:list-item}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-weight:500;line-height:1.2;margin-bottom:.5rem}.h1,h1{font-size:2.375rem}.h2,h2{font-size:1.9rem}.h3,h3{font-size:1.6625rem}.h4,h4{font-size:1.425rem}.h5,h5{font-size:1.1875rem}.h6,h6{font-size:.95rem}.lead{font-size:1.1875rem;font-weight:300}.display-1{font-size:6rem}.display-1,.display-2{font-weight:300;line-height:1.2}.display-2{font-size:5.5rem}.display-3{font-size:4.5rem}.display-3,.display-4{font-weight:300;line-height:1.2}.display-4{font-size:3.5rem}hr{border:0;border-top:1px solid rgba(0,0,0,.1);margin-bottom:1rem;margin-top:1rem}.small,small{font-size:.875em;font-weight:400}.mark,mark{background-color:#fcf8e3;padding:.2em}.list-inline,.list-unstyled{list-style:none;padding-left:0}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{font-size:1.1875rem;margin-bottom:1rem}.blockquote-footer{color:#6c757d;display:block;font-size:.875em}.blockquote-footer:before{content:"— "}.img-fluid,.img-thumbnail{height:auto;max-width:100%}.img-thumbnail{background-color:#ebebeb;border:1px solid #dee2e6;border-radius:.25rem;padding:.25rem}.figure{display:inline-block}.figure-img{line-height:1;margin-bottom:.5rem}.figure-caption{color:#6c757d;font-size:90%}code{word-wrap:break-word;color:#e83e8c;font-size:87.5%}a>code{color:inherit}kbd{background-color:#212529;border-radius:.2rem;color:#fff;font-size:87.5%;padding:.2rem .4rem}kbd kbd{font-size:100%;font-weight:700;padding:0}pre{color:#212529;display:block;font-size:87.5%}pre code{color:inherit;font-size:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl{margin-left:auto;margin-right:auto;padding-left:15px;padding-right:15px;width:100%}@media (min-width:2px){.container,.container-sm{max-width:1137px}}@media (min-width:8px){.container,.container-md,.container-sm{max-width:1138px}}@media (min-width:9px){.container,.container-lg,.container-md,.container-sm{max-width:1139px}}@media (min-width:10px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}.row{display:flex;flex-wrap:wrap;margin-left:-15px;margin-right:-15px}.no-gutters{margin-left:0;margin-right:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-left:0;padding-right:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{padding-left:15px;padding-right:15px;position:relative;width:100%}.col{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-1>*{flex:0 0 100%;max-width:100%}.row-cols-2>*{flex:0 0 50%;max-width:50%}.row-cols-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-4>*{flex:0 0 25%;max-width:25%}.row-cols-5>*{flex:0 0 20%;max-width:20%}.row-cols-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-auto{flex:0 0 auto;max-width:100%;width:auto}.col-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-3{flex:0 0 25%;max-width:25%}.col-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-6{flex:0 0 50%;max-width:50%}.col-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-9{flex:0 0 75%;max-width:75%}.col-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-12{flex:0 0 100%;max-width:100%}.order-first{order:-1}.order-last{order:13}.order-0{order:0}.order-1{order:1}.order-2{order:2}.order-3{order:3}.order-4{order:4}.order-5{order:5}.order-6{order:6}.order-7{order:7}.order-8{order:8}.order-9{order:9}.order-10{order:10}.order-11{order:11}.order-12{order:12}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}@media (min-width:2px){.col-sm{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-sm-1>*{flex:0 0 100%;max-width:100%}.row-cols-sm-2>*{flex:0 0 50%;max-width:50%}.row-cols-sm-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 25%;max-width:25%}.row-cols-sm-5>*{flex:0 0 20%;max-width:20%}.row-cols-sm-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-sm-auto{flex:0 0 auto;max-width:100%;width:auto}.col-sm-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-sm-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-sm-3{flex:0 0 25%;max-width:25%}.col-sm-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-sm-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-sm-6{flex:0 0 50%;max-width:50%}.col-sm-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-sm-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-sm-9{flex:0 0 75%;max-width:75%}.col-sm-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-sm-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-sm-12{flex:0 0 100%;max-width:100%}.order-sm-first{order:-1}.order-sm-last{order:13}.order-sm-0{order:0}.order-sm-1{order:1}.order-sm-2{order:2}.order-sm-3{order:3}.order-sm-4{order:4}.order-sm-5{order:5}.order-sm-6{order:6}.order-sm-7{order:7}.order-sm-8{order:8}.order-sm-9{order:9}.order-sm-10{order:10}.order-sm-11{order:11}.order-sm-12{order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}}@media (min-width:8px){.col-md{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-md-1>*{flex:0 0 100%;max-width:100%}.row-cols-md-2>*{flex:0 0 50%;max-width:50%}.row-cols-md-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-md-4>*{flex:0 0 25%;max-width:25%}.row-cols-md-5>*{flex:0 0 20%;max-width:20%}.row-cols-md-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-md-auto{flex:0 0 auto;max-width:100%;width:auto}.col-md-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-md-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-md-3{flex:0 0 25%;max-width:25%}.col-md-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-md-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-md-6{flex:0 0 50%;max-width:50%}.col-md-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-md-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-md-9{flex:0 0 75%;max-width:75%}.col-md-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-md-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-md-12{flex:0 0 100%;max-width:100%}.order-md-first{order:-1}.order-md-last{order:13}.order-md-0{order:0}.order-md-1{order:1}.order-md-2{order:2}.order-md-3{order:3}.order-md-4{order:4}.order-md-5{order:5}.order-md-6{order:6}.order-md-7{order:7}.order-md-8{order:8}.order-md-9{order:9}.order-md-10{order:10}.order-md-11{order:11}.order-md-12{order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}}@media (min-width:9px){.col-lg{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-lg-1>*{flex:0 0 100%;max-width:100%}.row-cols-lg-2>*{flex:0 0 50%;max-width:50%}.row-cols-lg-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 25%;max-width:25%}.row-cols-lg-5>*{flex:0 0 20%;max-width:20%}.row-cols-lg-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-lg-auto{flex:0 0 auto;max-width:100%;width:auto}.col-lg-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-lg-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-lg-3{flex:0 0 25%;max-width:25%}.col-lg-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-lg-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-lg-6{flex:0 0 50%;max-width:50%}.col-lg-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-lg-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-lg-9{flex:0 0 75%;max-width:75%}.col-lg-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-lg-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-lg-12{flex:0 0 100%;max-width:100%}.order-lg-first{order:-1}.order-lg-last{order:13}.order-lg-0{order:0}.order-lg-1{order:1}.order-lg-2{order:2}.order-lg-3{order:3}.order-lg-4{order:4}.order-lg-5{order:5}.order-lg-6{order:6}.order-lg-7{order:7}.order-lg-8{order:8}.order-lg-9{order:9}.order-lg-10{order:10}.order-lg-11{order:11}.order-lg-12{order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}}@media (min-width:10px){.col-xl{flex-basis:0;flex-grow:1;max-width:100%}.row-cols-xl-1>*{flex:0 0 100%;max-width:100%}.row-cols-xl-2>*{flex:0 0 50%;max-width:50%}.row-cols-xl-3>*{flex:0 0 33.3333333333%;max-width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 25%;max-width:25%}.row-cols-xl-5>*{flex:0 0 20%;max-width:20%}.row-cols-xl-6>*{flex:0 0 16.6666666667%;max-width:16.6666666667%}.col-xl-auto{flex:0 0 auto;max-width:100%;width:auto}.col-xl-1{flex:0 0 8.33333333%;max-width:8.33333333%}.col-xl-2{flex:0 0 16.66666667%;max-width:16.66666667%}.col-xl-3{flex:0 0 25%;max-width:25%}.col-xl-4{flex:0 0 33.33333333%;max-width:33.33333333%}.col-xl-5{flex:0 0 41.66666667%;max-width:41.66666667%}.col-xl-6{flex:0 0 50%;max-width:50%}.col-xl-7{flex:0 0 58.33333333%;max-width:58.33333333%}.col-xl-8{flex:0 0 66.66666667%;max-width:66.66666667%}.col-xl-9{flex:0 0 75%;max-width:75%}.col-xl-10{flex:0 0 83.33333333%;max-width:83.33333333%}.col-xl-11{flex:0 0 91.66666667%;max-width:91.66666667%}.col-xl-12{flex:0 0 100%;max-width:100%}.order-xl-first{order:-1}.order-xl-last{order:13}.order-xl-0{order:0}.order-xl-1{order:1}.order-xl-2{order:2}.order-xl-3{order:3}.order-xl-4{order:4}.order-xl-5{order:5}.order-xl-6{order:6}.order-xl-7{order:7}.order-xl-8{order:8}.order-xl-9{order:9}.order-xl-10{order:10}.order-xl-11{order:11}.order-xl-12{order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}}.table{color:#212529;margin-bottom:1rem;width:100%}.table td,.table th{border-top:1px solid #efefef;padding:.75rem;vertical-align:top}.table thead th{border-bottom:2px solid #efefef;vertical-align:bottom}.table tbody+tbody{border-top:2px solid #efefef}.table-sm td,.table-sm th{padding:.3rem}.table-bordered,.table-bordered td,.table-bordered th{border:1px solid #efefef}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{background-color:#f1f7fa;color:#212529}.table-primary,.table-primary>td,.table-primary>th{background-color:#d9cbfa}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#b89ff5}.table-hover .table-primary:hover,.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#c8b4f8}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#f5f7f8}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#eceff3}.table-hover .table-secondary:hover,.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#e6ebee}.table-success,.table-success>td,.table-success>th{background-color:#cef4de}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#a5ebc2}.table-hover .table-success:hover,.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b9efd0}.table-info,.table-info>td,.table-info>th{background-color:#ecf6fe}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#dceefc}.table-hover .table-info:hover,.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#d4ebfd}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffe5d2}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffcfac}.table-hover .table-warning:hover,.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffd6b9}.table-danger,.table-danger>td,.table-danger>th{background-color:#fbd0cf}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#f7a8a6}.table-hover .table-danger:hover,.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f9b9b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover,.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover,.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:#f1f7fa}.table-hover .table-active:hover,.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:#deecf3}.table .thead-dark th{background-color:#343a40;border-color:#454d55;color:#fff}.table .thead-light th{background-color:#e9ecef;border-color:#efefef;color:#495057}.table-dark{background-color:#343a40;color:#fff}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:hsla(0,0%,100%,.05)}.table-dark.table-hover tbody tr:hover{background-color:hsla(0,0%,100%,.075);color:#fff}@media (max-width:1.98px){.table-responsive-sm{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:7.98px){.table-responsive-md{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-md>.table-bordered{border:0}}@media (max-width:8.98px){.table-responsive-lg{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:9.98px){.table-responsive-xl{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{-webkit-overflow-scrolling:touch;display:block;overflow-x:auto;width:100%}.table-responsive>.table-bordered{border:0}.form-control{background-clip:padding-box;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;color:#495057;display:block;font-size:.95rem;font-weight:400;height:calc(1.5em + .75rem + 2px);line-height:1.5;padding:.375rem .75rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:100%}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{background-color:#fff;border-color:#ccbaf8;box-shadow:0 0 0 .2rem rgba(119,70,236,.25);color:#495057;outline:0}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}input[type=date].form-control,input[type=datetime-local].form-control,input[type=month].form-control,input[type=time].form-control{-webkit-appearance:none;-moz-appearance:none;appearance:none}select.form-control:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}select.form-control:focus::-ms-value{background-color:#fff;color:#495057}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{font-size:inherit;line-height:1.5;margin-bottom:0;padding-bottom:calc(.375rem + 1px);padding-top:calc(.375rem + 1px)}.col-form-label-lg{font-size:1.1875rem;line-height:1.5;padding-bottom:calc(.5rem + 1px);padding-top:calc(.5rem + 1px)}.col-form-label-sm{font-size:.83125rem;line-height:1.5;padding-bottom:calc(.25rem + 1px);padding-top:calc(.25rem + 1px)}.form-control-plaintext{background-color:transparent;border:solid transparent;border-width:1px 0;color:#212529;display:block;font-size:.95rem;line-height:1.5;margin-bottom:0;padding:.375rem 0;width:100%}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-left:0;padding-right:0}.form-control-sm{border-radius:.2rem;font-size:.83125rem;height:calc(1.5em + .5rem + 2px);line-height:1.5;padding:.25rem .5rem}.form-control-lg{border-radius:.3rem;font-size:1.1875rem;height:calc(1.5em + 1rem + 2px);line-height:1.5;padding:.5rem 1rem}select.form-control[multiple],select.form-control[size],textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:flex;flex-wrap:wrap;margin-left:-5px;margin-right:-5px}.form-row>.col,.form-row>[class*=col-]{padding-left:5px;padding-right:5px}.form-check{display:block;padding-left:1.25rem;position:relative}.form-check-input{margin-left:-1.25rem;margin-top:.3rem;position:absolute}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{align-items:center;display:inline-flex;margin-right:.75rem;padding-left:0}.form-check-inline .form-check-input{margin-left:0;margin-right:.3125rem;margin-top:0;position:static}.valid-feedback{color:#51d88a;display:none;font-size:.875em;margin-top:.25rem;width:100%}.valid-tooltip{background-color:rgba(81,216,138,.9);border-radius:.25rem;color:#212529;display:none;font-size:.83125rem;left:0;line-height:1.5;margin-top:.1rem;max-width:100%;padding:.25rem .5rem;position:absolute;top:100%;z-index:5}.form-row>.col>.valid-tooltip,.form-row>[class*=col-]>.valid-tooltip{left:5px}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%2351d88a' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);border-color:#51d88a;padding-right:calc(1.5em + .75rem)!important}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#51d88a;box-shadow:0 0 0 .2rem rgba(81,216,138,.25)}.was-validated select.form-control:valid,select.form-control.is-valid{background-position:right 1.5rem center;padding-right:3rem!important}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.custom-select.is-valid,.was-validated .custom-select:valid{background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0 0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%2351d88a' d='M2.3 6.73.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3E%3C/svg%3E") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat;border-color:#51d88a;padding-right:calc(.75em + 2.3125rem)!important}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#51d88a;box-shadow:0 0 0 .2rem rgba(81,216,138,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#51d88a}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#51d88a}.custom-control-input.is-valid~.custom-control-label:before,.was-validated .custom-control-input:valid~.custom-control-label:before{border-color:#51d88a}.custom-control-input.is-valid:checked~.custom-control-label:before,.was-validated .custom-control-input:valid:checked~.custom-control-label:before{background-color:#7be1a6;border-color:#7be1a6}.custom-control-input.is-valid:focus~.custom-control-label:before,.was-validated .custom-control-input:valid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(81,216,138,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label:before{border-color:#51d88a}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#51d88a}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#51d88a;box-shadow:0 0 0 .2rem rgba(81,216,138,.25)}.invalid-feedback{color:#ef5753;display:none;font-size:.875em;margin-top:.25rem;width:100%}.invalid-tooltip{background-color:rgba(239,87,83,.9);border-radius:.25rem;color:#fff;display:none;font-size:.83125rem;left:0;line-height:1.5;margin-top:.1rem;max-width:100%;padding:.25rem .5rem;position:absolute;top:100%;z-index:5}.form-row>.col>.invalid-tooltip,.form-row>[class*=col-]>.invalid-tooltip{left:5px}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23ef5753'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23ef5753' stroke='none'/%3E%3C/svg%3E");background-position:right calc(.375em + .1875rem) center;background-repeat:no-repeat;background-size:calc(.75em + .375rem) calc(.75em + .375rem);border-color:#ef5753;padding-right:calc(1.5em + .75rem)!important}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#ef5753;box-shadow:0 0 0 .2rem rgba(239,87,83,.25)}.was-validated select.form-control:invalid,select.form-control.is-invalid{background-position:right 1.5rem center;padding-right:3rem!important}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem);padding-right:calc(1.5em + .75rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0 0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat,#fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23ef5753'%3E%3Ccircle cx='6' cy='6' r='4.5'/%3E%3Cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3E%3Ccircle cx='6' cy='8.2' r='.6' fill='%23ef5753' stroke='none'/%3E%3C/svg%3E") center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem) no-repeat;border-color:#ef5753;padding-right:calc(.75em + 2.3125rem)!important}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#ef5753;box-shadow:0 0 0 .2rem rgba(239,87,83,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#ef5753}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#ef5753}.custom-control-input.is-invalid~.custom-control-label:before,.was-validated .custom-control-input:invalid~.custom-control-label:before{border-color:#ef5753}.custom-control-input.is-invalid:checked~.custom-control-label:before,.was-validated .custom-control-input:invalid:checked~.custom-control-label:before{background-color:#f38582;border-color:#f38582}.custom-control-input.is-invalid:focus~.custom-control-label:before,.was-validated .custom-control-input:invalid:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(239,87,83,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label:before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label:before{border-color:#ef5753}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#ef5753}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#ef5753;box-shadow:0 0 0 .2rem rgba(239,87,83,.25)}.form-inline{align-items:center;display:flex;flex-flow:row wrap}.form-inline .form-check{width:100%}@media (min-width:2px){.form-inline label{justify-content:center}.form-inline .form-group,.form-inline label{align-items:center;display:flex;margin-bottom:0}.form-inline .form-group{flex:0 0 auto;flex-flow:row wrap}.form-inline .form-control{display:inline-block;vertical-align:middle;width:auto}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{align-items:center;display:flex;justify-content:center;padding-left:0;width:auto}.form-inline .form-check-input{flex-shrink:0;margin-left:0;margin-right:.25rem;margin-top:0;position:relative}.form-inline .custom-control{align-items:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{background-color:transparent;border:1px solid transparent;border-radius:.25rem;color:#212529;display:inline-block;font-size:.95rem;font-weight:400;line-height:1.5;padding:.375rem .75rem;text-align:center;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{box-shadow:0 0 0 .2rem rgba(119,70,236,.25);outline:0}.btn.disabled,.btn:disabled{opacity:.65}.btn:not(:disabled):not(.disabled){cursor:pointer}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{background-color:#7746ec;border-color:#7746ec;color:#fff}.btn-primary.focus,.btn-primary:focus,.btn-primary:hover{background-color:#5e23e8;border-color:#5518e7;color:#fff}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 0 rgba(139,98,239,.5)}.btn-primary.disabled,.btn-primary:disabled{background-color:#7746ec;border-color:#7746ec;color:#fff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{background-color:#5518e7;border-color:#5117dc;color:#fff}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(139,98,239,.5)}.btn-secondary{background-color:#dae1e7;border-color:#dae1e7;color:#212529}.btn-secondary.focus,.btn-secondary:focus,.btn-secondary:hover{background-color:#c3ced8;border-color:#bbc8d3;color:#212529}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 0 rgba(190,197,203,.5)}.btn-secondary.disabled,.btn-secondary:disabled{background-color:#dae1e7;border-color:#dae1e7;color:#212529}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{background-color:#bbc8d3;border-color:#b3c2ce;color:#212529}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(190,197,203,.5)}.btn-success{background-color:#51d88a;border-color:#51d88a;color:#212529}.btn-success.focus,.btn-success:focus,.btn-success:hover{background-color:#32d175;border-color:#2dc96f;color:#212529}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 0 rgba(74,189,123,.5)}.btn-success.disabled,.btn-success:disabled{background-color:#51d88a;border-color:#51d88a;color:#212529}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{background-color:#2dc96f;border-color:#2bbf69;color:#fff}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(74,189,123,.5)}.btn-info{background-color:#bcdefa;border-color:#bcdefa;color:#212529}.btn-info.focus,.btn-info:focus,.btn-info:hover{background-color:#98ccf7;border-color:#8dc7f6;color:#212529}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 0 rgba(165,194,219,.5)}.btn-info.disabled,.btn-info:disabled{background-color:#bcdefa;border-color:#bcdefa;color:#212529}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{background-color:#8dc7f6;border-color:#81c1f6;color:#212529}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(165,194,219,.5)}.btn-warning{background-color:#ffa260;border-color:#ffa260;color:#212529}.btn-warning.focus,.btn-warning:focus,.btn-warning:hover{background-color:#ff8c3a;border-color:#ff842d;color:#212529}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 0 rgba(222,143,88,.5)}.btn-warning.disabled,.btn-warning:disabled{background-color:#ffa260;border-color:#ffa260;color:#212529}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{background-color:#ff842d;border-color:#ff7d20;color:#212529}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(222,143,88,.5)}.btn-danger{background-color:#ef5753;border-color:#ef5753;color:#fff}.btn-danger.focus,.btn-danger:focus,.btn-danger:hover{background-color:#ec3530;border-color:#eb2924;color:#fff}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 0 hsla(1,82%,69%,.5)}.btn-danger.disabled,.btn-danger:disabled{background-color:#ef5753;border-color:#ef5753;color:#fff}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{background-color:#eb2924;border-color:#ea1e19;color:#fff}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 0 hsla(1,82%,69%,.5)}.btn-light{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-light.focus,.btn-light:focus,.btn-light:hover{background-color:#e2e6ea;border-color:#dae0e5;color:#212529}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 0 hsla(220,4%,85%,.5)}.btn-light.disabled,.btn-light:disabled{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{background-color:#dae0e5;border-color:#d3d9df;color:#212529}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 0 hsla(220,4%,85%,.5)}.btn-dark{background-color:#343a40;border-color:#343a40;color:#fff}.btn-dark.focus,.btn-dark:focus,.btn-dark:hover{background-color:#23272b;border-color:#1d2124;color:#fff}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 0 rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{background-color:#343a40;border-color:#343a40;color:#fff}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{background-color:#1d2124;border-color:#171a1d;color:#fff}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(82,88,93,.5)}.btn-outline-primary{border-color:#7746ec;color:#7746ec}.btn-outline-primary:hover{background-color:#7746ec;border-color:#7746ec;color:#fff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 0 rgba(119,70,236,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{background-color:transparent;color:#7746ec}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{background-color:#7746ec;border-color:#7746ec;color:#fff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(119,70,236,.5)}.btn-outline-secondary{border-color:#dae1e7;color:#dae1e7}.btn-outline-secondary:hover{background-color:#dae1e7;border-color:#dae1e7;color:#212529}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 0 rgba(218,225,231,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{background-color:transparent;color:#dae1e7}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{background-color:#dae1e7;border-color:#dae1e7;color:#212529}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(218,225,231,.5)}.btn-outline-success{border-color:#51d88a;color:#51d88a}.btn-outline-success:hover{background-color:#51d88a;border-color:#51d88a;color:#212529}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 0 rgba(81,216,138,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{background-color:transparent;color:#51d88a}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{background-color:#51d88a;border-color:#51d88a;color:#212529}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(81,216,138,.5)}.btn-outline-info{border-color:#bcdefa;color:#bcdefa}.btn-outline-info:hover{background-color:#bcdefa;border-color:#bcdefa;color:#212529}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 0 rgba(188,222,250,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{background-color:transparent;color:#bcdefa}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{background-color:#bcdefa;border-color:#bcdefa;color:#212529}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(188,222,250,.5)}.btn-outline-warning{border-color:#ffa260;color:#ffa260}.btn-outline-warning:hover{background-color:#ffa260;border-color:#ffa260;color:#212529}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 0 rgba(255,162,96,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{background-color:transparent;color:#ffa260}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{background-color:#ffa260;border-color:#ffa260;color:#212529}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(255,162,96,.5)}.btn-outline-danger{border-color:#ef5753;color:#ef5753}.btn-outline-danger:hover{background-color:#ef5753;border-color:#ef5753;color:#fff}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 0 rgba(239,87,83,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{background-color:transparent;color:#ef5753}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{background-color:#ef5753;border-color:#ef5753;color:#fff}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(239,87,83,.5)}.btn-outline-light{border-color:#f8f9fa;color:#f8f9fa}.btn-outline-light:hover{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 0 rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{background-color:transparent;color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{background-color:#f8f9fa;border-color:#f8f9fa;color:#212529}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(248,249,250,.5)}.btn-outline-dark{border-color:#343a40;color:#343a40}.btn-outline-dark:hover{background-color:#343a40;border-color:#343a40;color:#fff}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 0 rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{background-color:transparent;color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{background-color:#343a40;border-color:#343a40;color:#fff}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 0 rgba(52,58,64,.5)}.btn-link{color:#7746ec;font-weight:400;text-decoration:none}.btn-link:hover{color:#4d15d0}.btn-link.focus,.btn-link:focus,.btn-link:hover{text-decoration:underline}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{border-radius:.3rem;font-size:1.1875rem;line-height:1.5;padding:.5rem 1rem}.btn-group-sm>.btn,.btn-sm{border-radius:.2rem;font-size:.83125rem;line-height:1.5;padding:.25rem .5rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;position:relative;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.width{height:auto;transition:width .35s ease;width:0}@media (prefers-reduced-motion:reduce){.collapsing.width{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle:after{border-bottom:0;border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:.3em solid;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropdown-toggle:empty:after{margin-left:0}.dropdown-menu{background-clip:padding-box;background-color:#fff;border:1px solid rgba(0,0,0,.15);border-radius:.25rem;color:#212529;display:none;float:left;font-size:.95rem;left:0;list-style:none;margin:.125rem 0 0;min-width:10rem;padding:.5rem 0;position:absolute;text-align:left;top:100%;z-index:1000}.dropdown-menu-left{left:0;right:auto}.dropdown-menu-right{left:auto;right:0}@media (min-width:2px){.dropdown-menu-sm-left{left:0;right:auto}.dropdown-menu-sm-right{left:auto;right:0}}@media (min-width:8px){.dropdown-menu-md-left{left:0;right:auto}.dropdown-menu-md-right{left:auto;right:0}}@media (min-width:9px){.dropdown-menu-lg-left{left:0;right:auto}.dropdown-menu-lg-right{left:auto;right:0}}@media (min-width:10px){.dropdown-menu-xl-left{left:0;right:auto}.dropdown-menu-xl-right{left:auto;right:0}}.dropup .dropdown-menu{bottom:100%;margin-bottom:.125rem;margin-top:0;top:auto}.dropup .dropdown-toggle:after{border-bottom:.3em solid;border-left:.3em solid transparent;border-right:.3em solid transparent;border-top:0;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropup .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-menu{left:100%;margin-left:.125rem;margin-top:0;right:auto;top:0}.dropright .dropdown-toggle:after{border-bottom:.3em solid transparent;border-left:.3em solid;border-right:0;border-top:.3em solid transparent;content:"";display:inline-block;margin-left:.255em;vertical-align:.255em}.dropright .dropdown-toggle:empty:after{margin-left:0}.dropright .dropdown-toggle:after{vertical-align:0}.dropleft .dropdown-menu{left:auto;margin-right:.125rem;margin-top:0;right:100%;top:0}.dropleft .dropdown-toggle:after{content:"";display:inline-block;display:none;margin-left:.255em;vertical-align:.255em}.dropleft .dropdown-toggle:before{border-bottom:.3em solid transparent;border-right:.3em solid;border-top:.3em solid transparent;content:"";display:inline-block;margin-right:.255em;vertical-align:.255em}.dropleft .dropdown-toggle:empty:after{margin-left:0}.dropleft .dropdown-toggle:before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{bottom:auto;right:auto}.dropdown-divider{border-top:1px solid #e9ecef;height:0;margin:.5rem 0;overflow:hidden}.dropdown-item{background-color:transparent;border:0;clear:both;color:#212529;display:block;font-weight:400;padding:.25rem 1.5rem;text-align:inherit;white-space:nowrap;width:100%}.dropdown-item:focus,.dropdown-item:hover{background-color:#e9ecef;color:#16181b;text-decoration:none}.dropdown-item.active,.dropdown-item:active{background-color:#7746ec;color:#fff;text-decoration:none}.dropdown-item.disabled,.dropdown-item:disabled{background-color:transparent;color:#adb5bd;pointer-events:none}.dropdown-menu.show{display:block}.dropdown-header{color:#6c757d;display:block;font-size:.83125rem;margin-bottom:0;padding:.5rem 1.5rem;white-space:nowrap}.dropdown-item-text{color:#212529;display:block;padding:.25rem 1.5rem}.btn-group,.btn-group-vertical{display:inline-flex;position:relative;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{flex:1 1 auto;position:relative}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.dropdown-toggle-split{padding-left:.5625rem;padding-right:.5625rem}.dropdown-toggle-split:after,.dropright .dropdown-toggle-split:after,.dropup .dropdown-toggle-split:after{margin-left:0}.dropleft .dropdown-toggle-split:before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-left:.375rem;padding-right:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-left:.75rem;padding-right:.75rem}.btn-group-vertical{align-items:flex-start;flex-direction:column;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-left-radius:0;border-bottom-right-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{clip:rect(0,0,0,0);pointer-events:none;position:absolute}.input-group{align-items:stretch;display:flex;flex-wrap:wrap;position:relative;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{flex:1 1 auto;margin-bottom:0;min-width:0;position:relative;width:1%}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.input-group>.custom-file{align-items:center;display:flex}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label:after{border-bottom-right-radius:0;border-top-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-bottom-left-radius:0;border-top-left-radius:0}.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label,.input-group.has-validation>.custom-file:nth-last-child(n+3) .custom-file-label:after,.input-group.has-validation>.custom-select:nth-last-child(n+3),.input-group.has-validation>.form-control:nth-last-child(n+3),.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label,.input-group:not(.has-validation)>.custom-file:not(:last-child) .custom-file-label:after,.input-group:not(.has-validation)>.custom-select:not(:last-child),.input-group:not(.has-validation)>.form-control:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.input-group-append,.input-group-prepend{display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{align-items:center;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem;color:#495057;display:flex;font-size:.95rem;font-weight:400;line-height:1.5;margin-bottom:0;padding:.375rem .75rem;text-align:center;white-space:nowrap}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{border-radius:.3rem;font-size:1.1875rem;line-height:1.5;padding:.5rem 1rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{border-radius:.2rem;font-size:.83125rem;line-height:1.5;padding:.25rem .5rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.btn,.input-group.has-validation>.input-group-append:nth-last-child(n+3)>.input-group-text,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.btn,.input-group:not(.has-validation)>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-bottom-right-radius:0;border-top-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-bottom-left-radius:0;border-top-left-radius:0}.custom-control{display:block;min-height:1.425rem;padding-left:1.5rem;position:relative;-webkit-print-color-adjust:exact;print-color-adjust:exact;z-index:1}.custom-control-inline{display:inline-flex;margin-right:1rem}.custom-control-input{height:1.2125rem;left:0;opacity:0;position:absolute;width:1rem;z-index:-1}.custom-control-input:checked~.custom-control-label:before{background-color:#7746ec;border-color:#7746ec;color:#fff}.custom-control-input:focus~.custom-control-label:before{box-shadow:0 0 0 .2rem rgba(119,70,236,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label:before{border-color:#ccbaf8}.custom-control-input:not(:disabled):active~.custom-control-label:before{background-color:#eee8fd;border-color:#eee8fd;color:#fff}.custom-control-input:disabled~.custom-control-label,.custom-control-input[disabled]~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label:before,.custom-control-input[disabled]~.custom-control-label:before{background-color:#e9ecef}.custom-control-label{margin-bottom:0;position:relative;vertical-align:top}.custom-control-label:before{background-color:#fff;border:1px solid #adb5bd;pointer-events:none}.custom-control-label:after,.custom-control-label:before{content:"";display:block;height:1rem;left:-1.5rem;position:absolute;top:.2125rem;width:1rem}.custom-control-label:after{background:50%/50% 50% no-repeat}.custom-checkbox .custom-control-label:before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8'%3E%3Cpath fill='%23fff' d='m6.564.75-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:before{background-color:#7746ec;border-color:#7746ec}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4'%3E%3Cpath stroke='%23fff' d='M0 2h4'/%3E%3C/svg%3E")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(119,70,236,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label:before{background-color:rgba(119,70,236,.5)}.custom-radio .custom-control-label:before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3E%3Ccircle r='3' fill='%23fff'/%3E%3C/svg%3E")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(119,70,236,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label:before{border-radius:.5rem;left:-2.25rem;pointer-events:all;width:1.75rem}.custom-switch .custom-control-label:after{background-color:#adb5bd;border-radius:.5rem;height:calc(1rem - 4px);left:calc(-2.25rem + 2px);top:calc(.2125rem + 2px);transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:calc(1rem - 4px)}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label:after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label:after{background-color:#fff;transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label:before{background-color:rgba(119,70,236,.5)}.custom-select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:#fff url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5'%3E%3Cpath fill='%23343a40' d='M2 0 0 2h4zm0 5L0 3h4z'/%3E%3C/svg%3E") right .75rem center/8px 10px no-repeat;border:1px solid #ced4da;border-radius:.25rem;color:#495057;display:inline-block;font-size:.95rem;font-weight:400;height:calc(1.5em + .75rem + 2px);line-height:1.5;padding:.375rem 1.75rem .375rem .75rem;vertical-align:middle;width:100%}.custom-select:focus{border-color:#ccbaf8;box-shadow:0 0 0 .2rem rgba(119,70,236,.25);outline:0}.custom-select:focus::-ms-value{background-color:#fff;color:#495057}.custom-select[multiple],.custom-select[size]:not([size="1"]){background-image:none;height:auto;padding-right:.75rem}.custom-select:disabled{background-color:#e9ecef;color:#6c757d}.custom-select::-ms-expand{display:none}.custom-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #495057}.custom-select-sm{font-size:.83125rem;height:calc(1.5em + .5rem + 2px);padding-bottom:.25rem;padding-left:.5rem;padding-top:.25rem}.custom-select-lg{font-size:1.1875rem;height:calc(1.5em + 1rem + 2px);padding-bottom:.5rem;padding-left:1rem;padding-top:.5rem}.custom-file{display:inline-block;margin-bottom:0}.custom-file,.custom-file-input{height:calc(1.5em + .75rem + 2px);position:relative;width:100%}.custom-file-input{margin:0;opacity:0;overflow:hidden;z-index:2}.custom-file-input:focus~.custom-file-label{border-color:#ccbaf8;box-shadow:0 0 0 .2rem rgba(119,70,236,.25)}.custom-file-input:disabled~.custom-file-label,.custom-file-input[disabled]~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label:after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]:after{content:attr(data-browse)}.custom-file-label{background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;font-weight:400;height:calc(1.5em + .75rem + 2px);left:0;overflow:hidden;z-index:1}.custom-file-label,.custom-file-label:after{color:#495057;line-height:1.5;padding:.375rem .75rem;position:absolute;right:0;top:0}.custom-file-label:after{background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0;bottom:0;content:"Browse";display:block;height:calc(1.5em + .75rem);z-index:3}.custom-range{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;height:1.4rem;padding:0;width:100%}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #ebebeb,0 0 0 .2rem rgba(119,70,236,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #ebebeb,0 0 0 .2rem rgba(119,70,236,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #ebebeb,0 0 0 .2rem rgba(119,70,236,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;background-color:#7746ec;border:0;border-radius:1rem;height:1rem;margin-top:-.25rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#eee8fd}.custom-range::-webkit-slider-runnable-track{background-color:#dee2e6;border-color:transparent;border-radius:1rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.custom-range::-moz-range-thumb{-moz-appearance:none;appearance:none;background-color:#7746ec;border:0;border-radius:1rem;height:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{-moz-transition:none;transition:none}}.custom-range::-moz-range-thumb:active{background-color:#eee8fd}.custom-range::-moz-range-track{background-color:#dee2e6;border-color:transparent;border-radius:1rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.custom-range::-ms-thumb{appearance:none;background-color:#7746ec;border:0;border-radius:1rem;height:1rem;margin-left:.2rem;margin-right:.2rem;margin-top:0;-ms-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;width:1rem}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{-ms-transition:none;transition:none}}.custom-range::-ms-thumb:active{background-color:#eee8fd}.custom-range::-ms-track{background-color:transparent;border-color:transparent;border-width:.5rem;color:transparent;cursor:pointer;height:.5rem;width:100%}.custom-range::-ms-fill-lower,.custom-range::-ms-fill-upper{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label:before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label:before,.custom-file-label,.custom-select{transition:none}}.nav{display:flex;flex-wrap:wrap;list-style:none;margin-bottom:0;padding-left:0}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;cursor:default;pointer-events:none}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{background-color:transparent;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem;margin-bottom:-1px}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{background-color:transparent;border-color:transparent;color:#6c757d}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{background-color:#ebebeb;border-color:#dee2e6 #dee2e6 #ebebeb;color:#495057}.nav-tabs .dropdown-menu{border-top-left-radius:0;border-top-right-radius:0;margin-top:-1px}.nav-pills .nav-link{background:none;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{background-color:#7746ec;color:#fff}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{padding:.5rem 1rem;position:relative}.navbar,.navbar .container,.navbar .container-fluid,.navbar .container-lg,.navbar .container-md,.navbar .container-sm,.navbar .container-xl{align-items:center;display:flex;flex-wrap:wrap;justify-content:space-between}.navbar-brand{display:inline-block;font-size:1.1875rem;line-height:inherit;margin-right:1rem;padding-bottom:.321875rem;padding-top:.321875rem;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:flex;flex-direction:column;list-style:none;margin-bottom:0;padding-left:0}.navbar-nav .nav-link{padding-left:0;padding-right:0}.navbar-nav .dropdown-menu{float:none;position:static}.navbar-text{display:inline-block;padding-bottom:.5rem;padding-top:.5rem}.navbar-collapse{align-items:center;flex-basis:100%;flex-grow:1}.navbar-toggler{background-color:transparent;border:1px solid transparent;border-radius:.25rem;font-size:1.1875rem;line-height:1;padding:.25rem .75rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{background:50%/100% 100% no-repeat;content:"";display:inline-block;height:1.5em;vertical-align:middle;width:1.5em}.navbar-nav-scroll{max-height:75vh;overflow-y:auto}@media (max-width:1.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{padding-left:0;padding-right:0}}@media (min-width:2px){.navbar-expand-sm{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid,.navbar-expand-sm>.container-lg,.navbar-expand-sm>.container-md,.navbar-expand-sm>.container-sm,.navbar-expand-sm>.container-xl{flex-wrap:nowrap}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:7.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{padding-left:0;padding-right:0}}@media (min-width:8px){.navbar-expand-md{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid,.navbar-expand-md>.container-lg,.navbar-expand-md>.container-md,.navbar-expand-md>.container-sm,.navbar-expand-md>.container-xl{flex-wrap:nowrap}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:8.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{padding-left:0;padding-right:0}}@media (min-width:9px){.navbar-expand-lg{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid,.navbar-expand-lg>.container-lg,.navbar-expand-lg>.container-md,.navbar-expand-lg>.container-sm,.navbar-expand-lg>.container-xl{flex-wrap:nowrap}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:9.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{padding-left:0;padding-right:0}}@media (min-width:10px){.navbar-expand-xl{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid,.navbar-expand-xl>.container-lg,.navbar-expand-xl>.container-md,.navbar-expand-xl>.container-sm,.navbar-expand-xl>.container-xl{flex-wrap:nowrap}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{flex-flow:row nowrap;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{padding-left:0;padding-right:0}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-left:.5rem;padding-right:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid,.navbar-expand>.container-lg,.navbar-expand>.container-md,.navbar-expand>.container-sm,.navbar-expand>.container-xl{flex-wrap:nowrap}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand,.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{border-color:rgba(0,0,0,.1);color:rgba(0,0,0,.5)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(0, 0, 0, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand,.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:hsla(0,0%,100%,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:hsla(0,0%,100%,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{border-color:hsla(0,0%,100%,.1);color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Cpath stroke='rgba(255, 255, 255, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E")}.navbar-dark .navbar-text{color:hsla(0,0%,100%,.5)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{word-wrap:break-word;background-clip:border-box;background-color:#fff;border:1px solid rgba(0,0,0,.125);border-radius:.25rem;display:flex;flex-direction:column;min-width:0;position:relative}.card>hr{margin-left:0;margin-right:0}.card>.list-group{border-bottom:inherit;border-top:inherit}.card>.list-group:first-child{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px);border-top-width:0}.card>.list-group:last-child{border-bottom-left-radius:calc(.25rem - 1px);border-bottom-right-radius:calc(.25rem - 1px);border-bottom-width:0}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;min-height:1px;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem}.card-subtitle,.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{background-color:#fff;border-bottom:1px solid rgba(0,0,0,.125);margin-bottom:0;padding:.75rem 1.25rem}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{background-color:#fff;border-top:1px solid rgba(0,0,0,.125);padding:.75rem 1.25rem}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{border-bottom:0;margin-bottom:-.75rem}.card-header-pills,.card-header-tabs{margin-left:-.625rem;margin-right:-.625rem}.card-img-overlay{border-radius:calc(.25rem - 1px);bottom:0;left:0;padding:1.25rem;position:absolute;right:0;top:0}.card-img,.card-img-bottom,.card-img-top{flex-shrink:0;width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-left-radius:calc(.25rem - 1px);border-bottom-right-radius:calc(.25rem - 1px)}.card-deck .card{margin-bottom:15px}@media (min-width:2px){.card-deck{display:flex;flex-flow:row wrap;margin-left:-15px;margin-right:-15px}.card-deck .card{flex:1 0 0%;margin-bottom:0;margin-left:15px;margin-right:15px}}.card-group>.card{margin-bottom:15px}@media (min-width:2px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{border-left:0;margin-left:0}.card-group>.card:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:2px){.card-columns{-moz-column-count:3;column-count:3;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion{overflow-anchor:none}.accordion>.card{overflow:hidden}.accordion>.card:not(:last-of-type){border-bottom:0;border-bottom-left-radius:0;border-bottom-right-radius:0}.accordion>.card:not(:first-of-type){border-top-left-radius:0;border-top-right-radius:0}.accordion>.card>.card-header{border-radius:0;margin-bottom:-1px}.breadcrumb{background-color:#e9ecef;border-radius:.25rem;display:flex;flex-wrap:wrap;list-style:none;margin-bottom:1rem;padding:.75rem 1rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item:before{color:#6c757d;content:"/";float:left;padding-right:.5rem}.breadcrumb-item+.breadcrumb-item:hover:before{text-decoration:underline;text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{border-radius:.25rem;display:flex;list-style:none;padding-left:0}.page-link{background-color:#fff;border:1px solid #dee2e6;color:#7746ec;display:block;line-height:1.25;margin-left:-1px;padding:.5rem .75rem;position:relative}.page-link:hover{background-color:#e9ecef;border-color:#dee2e6;color:#4d15d0;text-decoration:none;z-index:2}.page-link:focus{box-shadow:0 0 0 .2rem rgba(119,70,236,.25);outline:0;z-index:3}.page-item:first-child .page-link{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem;margin-left:0}.page-item:last-child .page-link{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.page-item.active .page-link{background-color:#7746ec;border-color:#7746ec;color:#fff;z-index:3}.page-item.disabled .page-link{background-color:#fff;border-color:#dee2e6;color:#6c757d;cursor:auto;pointer-events:none}.pagination-lg .page-link{font-size:1.1875rem;line-height:1.5;padding:.75rem 1.5rem}.pagination-lg .page-item:first-child .page-link{border-bottom-left-radius:.3rem;border-top-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-bottom-right-radius:.3rem;border-top-right-radius:.3rem}.pagination-sm .page-link{font-size:.83125rem;line-height:1.5;padding:.25rem .5rem}.pagination-sm .page-item:first-child .page-link{border-bottom-left-radius:.2rem;border-top-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-bottom-right-radius:.2rem;border-top-right-radius:.2rem}.badge{border-radius:.25rem;display:inline-block;font-size:.95rem;font-weight:700;line-height:1;padding:.25em .4em;text-align:center;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;vertical-align:baseline;white-space:nowrap}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{border-radius:10rem;padding-left:.6em;padding-right:.6em}.badge-primary{background-color:#7746ec;color:#fff}a.badge-primary:focus,a.badge-primary:hover{background-color:#5518e7;color:#fff}a.badge-primary.focus,a.badge-primary:focus{box-shadow:0 0 0 .2rem rgba(119,70,236,.5);outline:0}.badge-secondary{background-color:#dae1e7;color:#212529}a.badge-secondary:focus,a.badge-secondary:hover{background-color:#bbc8d3;color:#212529}a.badge-secondary.focus,a.badge-secondary:focus{box-shadow:0 0 0 .2rem rgba(218,225,231,.5);outline:0}.badge-success{background-color:#51d88a;color:#212529}a.badge-success:focus,a.badge-success:hover{background-color:#2dc96f;color:#212529}a.badge-success.focus,a.badge-success:focus{box-shadow:0 0 0 .2rem rgba(81,216,138,.5);outline:0}.badge-info{background-color:#bcdefa;color:#212529}a.badge-info:focus,a.badge-info:hover{background-color:#8dc7f6;color:#212529}a.badge-info.focus,a.badge-info:focus{box-shadow:0 0 0 .2rem rgba(188,222,250,.5);outline:0}.badge-warning{background-color:#ffa260;color:#212529}a.badge-warning:focus,a.badge-warning:hover{background-color:#ff842d;color:#212529}a.badge-warning.focus,a.badge-warning:focus{box-shadow:0 0 0 .2rem rgba(255,162,96,.5);outline:0}.badge-danger{background-color:#ef5753;color:#fff}a.badge-danger:focus,a.badge-danger:hover{background-color:#eb2924;color:#fff}a.badge-danger.focus,a.badge-danger:focus{box-shadow:0 0 0 .2rem rgba(239,87,83,.5);outline:0}.badge-light{background-color:#f8f9fa;color:#212529}a.badge-light:focus,a.badge-light:hover{background-color:#dae0e5;color:#212529}a.badge-light.focus,a.badge-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5);outline:0}.badge-dark{background-color:#343a40;color:#fff}a.badge-dark:focus,a.badge-dark:hover{background-color:#1d2124;color:#fff}a.badge-dark.focus,a.badge-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5);outline:0}.jumbotron{background-color:#e9ecef;border-radius:.3rem;margin-bottom:2rem;padding:2rem 1rem}@media (min-width:2px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{border-radius:0;padding-left:0;padding-right:0}.alert{border:1px solid transparent;border-radius:.25rem;margin-bottom:1rem;padding:.75rem 1.25rem;position:relative}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3.925rem}.alert-dismissible .close{color:inherit;padding:.75rem 1.25rem;position:absolute;right:0;top:0;z-index:2}.alert-primary{background-color:#e4dafb;border-color:#d9cbfa;color:#3e247b}.alert-primary hr{border-top-color:#c8b4f8}.alert-primary .alert-link{color:#2a1854}.alert-secondary{background-color:#f8f9fa;border-color:#f5f7f8;color:#717578}.alert-secondary hr{border-top-color:#e6ebee}.alert-secondary .alert-link{color:#585b5e}.alert-success{background-color:#dcf7e8;border-color:#cef4de;color:#2a7048}.alert-success hr{border-top-color:#b9efd0}.alert-success .alert-link{color:#1c4b30}.alert-info{background-color:#f2f8fe;border-color:#ecf6fe;color:#627382}.alert-info hr{border-top-color:#d4ebfd}.alert-info .alert-link{color:#4c5965}.alert-warning{background-color:#ffecdf;border-color:#ffe5d2;color:#855432}.alert-warning hr{border-top-color:#ffd6b9}.alert-warning .alert-link{color:#603d24}.alert-danger{background-color:#fcdddd;border-color:#fbd0cf;color:#7c2d2b}.alert-danger hr{border-top-color:#f9b9b7}.alert-danger .alert-link{color:#561f1e}.alert-light{background-color:#fefefe;border-color:#fdfdfe;color:#818182}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{background-color:#d6d8d9;border-color:#c6c8ca;color:#1b1e21}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@keyframes progress-bar-stripes{0%{background-position:1rem 0}to{background-position:0 0}}.progress{background-color:#e9ecef;border-radius:.25rem;font-size:.7125rem;height:1rem;line-height:0}.progress,.progress-bar{display:flex;overflow:hidden}.progress-bar{background-color:#7746ec;color:#fff;flex-direction:column;justify-content:center;text-align:center;transition:width .6s ease;white-space:nowrap}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,hsla(0,0%,100%,.15) 25%,transparent 0,transparent 50%,hsla(0,0%,100%,.15) 0,hsla(0,0%,100%,.15) 75%,transparent 0,transparent);background-size:1rem 1rem}.progress-bar-animated{animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{animation:none}}.media{align-items:flex-start;display:flex}.media-body{flex:1}.list-group{border-radius:.25rem;display:flex;flex-direction:column;margin-bottom:0;padding-left:0}.list-group-item-action{color:#495057;text-align:inherit;width:100%}.list-group-item-action:focus,.list-group-item-action:hover{background-color:#f8f9fa;color:#495057;text-decoration:none;z-index:1}.list-group-item-action:active{background-color:#e9ecef;color:#212529}.list-group-item{background-color:#fff;border:1px solid rgba(0,0,0,.125);display:block;padding:.75rem 1.25rem;position:relative}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{background-color:#fff;color:#6c757d;pointer-events:none}.list-group-item.active{background-color:#7746ec;border-color:#7746ec;color:#fff;z-index:2}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{border-top-width:1px;margin-top:-1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}@media (min-width:2px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}@media (min-width:8px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-md>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}@media (min-width:9px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}@media (min-width:10px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-bottom-left-radius:0;border-top-right-radius:.25rem}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-left-width:0;border-top-width:1px}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{border-left-width:1px;margin-left:-1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{background-color:#d9cbfa;color:#3e247b}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{background-color:#c8b4f8;color:#3e247b}.list-group-item-primary.list-group-item-action.active{background-color:#3e247b;border-color:#3e247b;color:#fff}.list-group-item-secondary{background-color:#f5f7f8;color:#717578}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{background-color:#e6ebee;color:#717578}.list-group-item-secondary.list-group-item-action.active{background-color:#717578;border-color:#717578;color:#fff}.list-group-item-success{background-color:#cef4de;color:#2a7048}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{background-color:#b9efd0;color:#2a7048}.list-group-item-success.list-group-item-action.active{background-color:#2a7048;border-color:#2a7048;color:#fff}.list-group-item-info{background-color:#ecf6fe;color:#627382}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{background-color:#d4ebfd;color:#627382}.list-group-item-info.list-group-item-action.active{background-color:#627382;border-color:#627382;color:#fff}.list-group-item-warning{background-color:#ffe5d2;color:#855432}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{background-color:#ffd6b9;color:#855432}.list-group-item-warning.list-group-item-action.active{background-color:#855432;border-color:#855432;color:#fff}.list-group-item-danger{background-color:#fbd0cf;color:#7c2d2b}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{background-color:#f9b9b7;color:#7c2d2b}.list-group-item-danger.list-group-item-action.active{background-color:#7c2d2b;border-color:#7c2d2b;color:#fff}.list-group-item-light{background-color:#fdfdfe;color:#818182}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{background-color:#ececf6;color:#818182}.list-group-item-light.list-group-item-action.active{background-color:#818182;border-color:#818182;color:#fff}.list-group-item-dark{background-color:#c6c8ca;color:#1b1e21}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{background-color:#b9bbbe;color:#1b1e21}.list-group-item-dark.list-group-item-action.active{background-color:#1b1e21;border-color:#1b1e21;color:#fff}.close{color:#000;float:right;font-size:1.425rem;font-weight:700;line-height:1;opacity:.5;text-shadow:0 1px 0 #fff}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{background-color:transparent;border:0;padding:0}a.close.disabled{pointer-events:none}.toast{background-clip:padding-box;background-color:hsla(0,0%,100%,.85);border:1px solid rgba(0,0,0,.1);border-radius:.25rem;box-shadow:0 .25rem .75rem rgba(0,0,0,.1);flex-basis:350px;font-size:.875rem;max-width:350px;opacity:0}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{align-items:center;background-clip:padding-box;background-color:hsla(0,0%,100%,.85);border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px);color:#6c757d;display:flex;padding:.25rem .75rem}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{display:none;height:100%;left:0;outline:0;overflow:hidden;position:fixed;top:0;width:100%;z-index:1050}.modal-dialog{margin:.5rem;pointer-events:none;position:relative;width:auto}.modal.fade .modal-dialog{transform:translateY(-50px);transition:transform .3s ease-out}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{align-items:center;display:flex;min-height:calc(100% - 1rem)}.modal-dialog-centered:before{content:"";display:block;height:calc(100vh - 1rem);height:-moz-min-content;height:min-content}.modal-dialog-centered.modal-dialog-scrollable{flex-direction:column;height:100%;justify-content:center}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable:before{content:none}.modal-content{background-clip:padding-box;background-color:#fff;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;display:flex;flex-direction:column;outline:0;pointer-events:auto;position:relative;width:100%}.modal-backdrop{background-color:#000;height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:1040}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{align-items:flex-start;border-bottom:1px solid #efefef;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px);display:flex;justify-content:space-between;padding:1rem}.modal-header .close{margin:-1rem -1rem -1rem auto;padding:1rem}.modal-title{line-height:1.5;margin-bottom:0}.modal-body{flex:1 1 auto;padding:1rem;position:relative}.modal-footer{align-items:center;border-bottom-left-radius:calc(.3rem - 1px);border-bottom-right-radius:calc(.3rem - 1px);border-top:1px solid #efefef;display:flex;flex-wrap:wrap;justify-content:flex-end;padding:.75rem}.modal-footer>*{margin:.25rem}.modal-scrollbar-measure{height:50px;overflow:scroll;position:absolute;top:-9999px;width:50px}@media (min-width:2px){.modal-dialog{margin:1.75rem auto;max-width:500px}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered:before{height:calc(100vh - 3.5rem);height:-moz-min-content;height:min-content}.modal-sm{max-width:300px}}@media (min-width:9px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:10px){.modal-xl{max-width:1140px}}.tooltip{word-wrap:break-word;display:block;font-family:Nunito,sans-serif;font-size:.83125rem;font-style:normal;font-weight:400;letter-spacing:normal;line-break:auto;line-height:1.5;margin:0;opacity:0;position:absolute;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;white-space:normal;word-break:normal;word-spacing:normal;z-index:1070}.tooltip.show{opacity:.9}.tooltip .arrow{display:block;height:.4rem;position:absolute;width:.8rem}.tooltip .arrow:before{border-color:transparent;border-style:solid;content:"";position:absolute}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow:before,.bs-tooltip-top .arrow:before{border-top-color:#000;border-width:.4rem .4rem 0;top:0}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{height:.8rem;left:0;width:.4rem}.bs-tooltip-auto[x-placement^=right] .arrow:before,.bs-tooltip-right .arrow:before{border-right-color:#000;border-width:.4rem .4rem .4rem 0;right:0}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow:before,.bs-tooltip-bottom .arrow:before{border-bottom-color:#000;border-width:0 .4rem .4rem;bottom:0}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{height:.8rem;right:0;width:.4rem}.bs-tooltip-auto[x-placement^=left] .arrow:before,.bs-tooltip-left .arrow:before{border-left-color:#000;border-width:.4rem 0 .4rem .4rem;left:0}.tooltip-inner{background-color:#000;border-radius:.25rem;color:#fff;max-width:200px;padding:.25rem .5rem;text-align:center}.popover{word-wrap:break-word;background-clip:padding-box;background-color:#fff;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;font-family:Nunito,sans-serif;font-size:.83125rem;font-style:normal;font-weight:400;left:0;letter-spacing:normal;line-break:auto;line-height:1.5;max-width:276px;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;top:0;white-space:normal;word-break:normal;word-spacing:normal;z-index:1060}.popover,.popover .arrow{display:block;position:absolute}.popover .arrow{height:.5rem;margin:0 .3rem;width:1rem}.popover .arrow:after,.popover .arrow:before{border-color:transparent;border-style:solid;content:"";display:block;position:absolute}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=top]>.arrow:before,.bs-popover-top>.arrow:before{border-top-color:rgba(0,0,0,.25);border-width:.5rem .5rem 0;bottom:0}.bs-popover-auto[x-placement^=top]>.arrow:after,.bs-popover-top>.arrow:after{border-top-color:#fff;border-width:.5rem .5rem 0;bottom:1px}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{height:1rem;left:calc(-.5rem - 1px);margin:.3rem 0;width:.5rem}.bs-popover-auto[x-placement^=right]>.arrow:before,.bs-popover-right>.arrow:before{border-right-color:rgba(0,0,0,.25);border-width:.5rem .5rem .5rem 0;left:0}.bs-popover-auto[x-placement^=right]>.arrow:after,.bs-popover-right>.arrow:after{border-right-color:#fff;border-width:.5rem .5rem .5rem 0;left:1px}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[x-placement^=bottom]>.arrow:before,.bs-popover-bottom>.arrow:before{border-bottom-color:rgba(0,0,0,.25);border-width:0 .5rem .5rem;top:0}.bs-popover-auto[x-placement^=bottom]>.arrow:after,.bs-popover-bottom>.arrow:after{border-bottom-color:#fff;border-width:0 .5rem .5rem;top:1px}.bs-popover-auto[x-placement^=bottom] .popover-header:before,.bs-popover-bottom .popover-header:before{border-bottom:1px solid #f7f7f7;content:"";display:block;left:50%;margin-left:-.5rem;position:absolute;top:0;width:1rem}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{height:1rem;margin:.3rem 0;right:calc(-.5rem - 1px);width:.5rem}.bs-popover-auto[x-placement^=left]>.arrow:before,.bs-popover-left>.arrow:before{border-left-color:rgba(0,0,0,.25);border-width:.5rem 0 .5rem .5rem;right:0}.bs-popover-auto[x-placement^=left]>.arrow:after,.bs-popover-left>.arrow:after{border-left-color:#fff;border-width:.5rem 0 .5rem .5rem;right:1px}.popover-header{background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px);font-size:.95rem;margin-bottom:0;padding:.5rem .75rem}.popover-header:empty{display:none}.popover-body{color:#212529;padding:.5rem .75rem}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{overflow:hidden;position:relative;width:100%}.carousel-inner:after{clear:both;content:"";display:block}.carousel-item{-webkit-backface-visibility:hidden;backface-visibility:hidden;display:none;float:left;margin-right:-100%;position:relative;transition:transform .6s ease-in-out;width:100%}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transform:none;transition-property:opacity}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{opacity:1;z-index:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{opacity:0;transition:opacity 0s .6s;z-index:0}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{align-items:center;background:none;border:0;bottom:0;color:#fff;display:flex;justify-content:center;opacity:.5;padding:0;position:absolute;text-align:center;top:0;transition:opacity .15s ease;width:15%;z-index:1}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;opacity:.9;outline:0;text-decoration:none}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{background:50%/100% 100% no-repeat;display:inline-block;height:20px;width:20px}.carousel-control-prev-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='m5.25 0-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3E%3C/svg%3E")}.carousel-control-next-icon{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8'%3E%3Cpath d='m2.75 0-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3E%3C/svg%3E")}.carousel-indicators{bottom:0;display:flex;justify-content:center;left:0;list-style:none;margin-left:15%;margin-right:15%;padding-left:0;position:absolute;right:0;z-index:15}.carousel-indicators li{background-clip:padding-box;background-color:#fff;border-bottom:10px solid transparent;border-top:10px solid transparent;box-sizing:content-box;cursor:pointer;flex:0 1 auto;height:3px;margin-left:3px;margin-right:3px;opacity:.5;text-indent:-999px;transition:opacity .6s ease;width:30px}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{bottom:20px;color:#fff;left:15%;padding-bottom:20px;padding-top:20px;position:absolute;right:15%;text-align:center;z-index:10}@keyframes spinner-border{to{transform:rotate(1turn)}}.spinner-border{animation:spinner-border .75s linear infinite;border:.25em solid;border-radius:50%;border-right:.25em solid transparent;display:inline-block;height:2rem;vertical-align:-.125em;width:2rem}.spinner-border-sm{border-width:.2em;height:1rem;width:1rem}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{animation:spinner-grow .75s linear infinite;background-color:currentcolor;border-radius:50%;display:inline-block;height:2rem;opacity:0;vertical-align:-.125em;width:2rem}.spinner-grow-sm{height:1rem;width:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{animation-duration:1.5s}}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#7746ec!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#5518e7!important}.bg-secondary{background-color:#dae1e7!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#bbc8d3!important}.bg-success{background-color:#51d88a!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#2dc96f!important}.bg-info{background-color:#bcdefa!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#8dc7f6!important}.bg-warning{background-color:#ffa260!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#ff842d!important}.bg-danger{background-color:#ef5753!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#eb2924!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #efefef!important}.border-top{border-top:1px solid #efefef!important}.border-right{border-right:1px solid #efefef!important}.border-bottom{border-bottom:1px solid #efefef!important}.border-left{border-left:1px solid #efefef!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#7746ec!important}.border-secondary{border-color:#dae1e7!important}.border-success{border-color:#51d88a!important}.border-info{border-color:#bcdefa!important}.border-warning{border-color:#ffa260!important}.border-danger{border-color:#ef5753!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important}.rounded-right,.rounded-top{border-top-right-radius:.25rem!important}.rounded-bottom,.rounded-right{border-bottom-right-radius:.25rem!important}.rounded-bottom,.rounded-left{border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix:after{clear:both;content:"";display:block}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}@media (min-width:2px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}}@media (min-width:8px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}}@media (min-width:9px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}}@media (min-width:10px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}}.embed-responsive{display:block;overflow:hidden;padding:0;position:relative;width:100%}.embed-responsive:before{content:"";display:block}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{border:0;bottom:0;height:100%;left:0;position:absolute;top:0;width:100%}.embed-responsive-21by9:before{padding-top:42.85714286%}.embed-responsive-16by9:before{padding-top:56.25%}.embed-responsive-4by3:before{padding-top:75%}.embed-responsive-1by1:before{padding-top:100%}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-fill{flex:1 1 auto!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}@media (min-width:2px){.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}}@media (min-width:8px){.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}}@media (min-width:9px){.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}}@media (min-width:10px){.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:2px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:8px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:9px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:10px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:sticky!important}.fixed-top{top:0}.fixed-bottom,.fixed-top{left:0;position:fixed;right:0;z-index:1030}.fixed-bottom{bottom:0}@supports (position:sticky){.sticky-top{position:sticky;top:0;z-index:1020}}.sr-only{clip:rect(0,0,0,0);border:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;overflow:visible;position:static;white-space:normal;width:auto}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:2px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:8px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:9px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:10px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.stretched-link:after{background-color:transparent;bottom:0;content:"";left:0;pointer-events:auto;position:absolute;right:0;top:0;z-index:1}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:2px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:8px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:9px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:10px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#7746ec!important}a.text-primary:focus,a.text-primary:hover{color:#4d15d0!important}.text-secondary{color:#dae1e7!important}a.text-secondary:focus,a.text-secondary:hover{color:#acbbc9!important}.text-success{color:#51d88a!important}a.text-success:focus,a.text-success:hover{color:#28b463!important}.text-info{color:#bcdefa!important}a.text-info:focus,a.text-info:hover{color:#75bbf5!important}.text-warning{color:#ffa260!important}a.text-warning:focus,a.text-warning:hover{color:#ff7514!important}.text-danger{color:#ef5753!important}a.text-danger:focus,a.text-danger:hover{color:#e11a15!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:hsla(0,0%,100%,.5)!important}.text-hide{background-color:transparent;border:0;color:transparent;font:0/0 a;text-shadow:none}.text-decoration-none{text-decoration:none!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,:after,:before{box-shadow:none!important;text-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]:after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd}blockquote,img,pre,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}.container,body{min-width:9px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#efefef}.table .thead-dark th{border-color:#efefef;color:inherit}}body{padding-bottom:20px}.container{width:1140px}html{min-width:1140px}[v-cloak]{display:none}svg.icon{height:1rem;width:1rem}.header{border-bottom:1px solid #d5dfe9}.header svg.logo{height:2rem;width:2rem}.sidebar .nav-item a{color:#2a5164;padding:.5rem 0}.sidebar .nav-item a svg{fill:#c3cbd3;height:1rem;margin-right:15px;width:1rem}.sidebar .nav-item a.active{color:#7746ec}.sidebar .nav-item a.active svg{fill:#7746ec}.card{border:none;box-shadow:0 2px 3px #cdd8df}.card .bottom-radius{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card .card-header{background-color:#fff;border-bottom:none;padding-bottom:.7rem;padding-top:.7rem}.card .card-header .btn-group .btn{padding:.2rem .5rem}.card .card-header h5{margin:0}.card .table td,.card .table th{padding:.75rem 1.25rem}.card .table.table-sm td,.card .table.table-sm th{padding:1rem 1.25rem}.card .table th{background-color:#f3f4f6;border-bottom:0;font-weight:400;padding:.5rem 1.25rem}.card .table:not(.table-borderless) td{border-top:1px solid #efefef}.card .table.penultimate-column-right td:nth-last-child(2),.card .table.penultimate-column-right th:nth-last-child(2){text-align:right}.card .table td.table-fit,.card .table th.table-fit{white-space:nowrap;width:1%}.fill-text-color{fill:#212529}.fill-danger{fill:#ef5753}.fill-warning{fill:#ffa260}.fill-info{fill:#bcdefa}.fill-success{fill:#51d88a}.fill-primary{fill:#7746ec}button:hover .fill-primary{fill:#fff}.btn-outline-primary.active .fill-primary{fill:#ebebeb}.btn-outline-primary:not(:disabled):not(.disabled).active:focus{box-shadow:none!important}.control-action svg{fill:#ccd2df;height:1.2rem;width:1.2rem}.control-action svg:hover{fill:#7746ec}.info-icon{fill:#ccd2df}.paginator .btn{color:#9ea7ac;text-decoration:none}.paginator .btn:hover{color:#7746ec}@keyframes spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.spin{animation:spin 2s linear infinite}.card .nav-pills .nav-link.active{background:none;border-bottom:2px solid #7746ec;color:#7746ec}.card .nav-pills .nav-link{border-radius:0;color:#212529;font-size:.9rem;padding:.75rem 1.25rem}.list-enter-active:not(.dontanimate){transition:background 1s linear}.list-enter:not(.dontanimate),.list-leave-to:not(.dontanimate){background:#fffee9}.card table td{vertical-align:middle!important}.card-bg-secondary{background:#fafafa}.code-bg{background:#120f12}.disabled-watcher{background:#ef5753;color:#fff;padding:.75rem}.badge-sm{font-size:.75rem} diff --git a/public/vendor/horizon/app.js b/public/vendor/horizon/app.js new file mode 100644 index 00000000..8a938166 --- /dev/null +++ b/public/vendor/horizon/app.js @@ -0,0 +1,2 @@ +/*! For license information please see app.js.LICENSE.txt */ +(()=>{var t,e={9669:(t,e,o)=>{t.exports=o(1609)},5448:(t,e,o)=>{"use strict";var n=o(4867),p=o(6026),b=o(4372),M=o(5327),z=o(4097),r=o(4109),c=o(7985),i=o(5061);t.exports=function(t){return new Promise((function(e,o){var a=t.data,O=t.headers,s=t.responseType;n.isFormData(a)&&delete O["Content-Type"];var l=new XMLHttpRequest;if(t.auth){var d=t.auth.username||"",u=t.auth.password?unescape(encodeURIComponent(t.auth.password)):"";O.Authorization="Basic "+btoa(d+":"+u)}var A=z(t.baseURL,t.url);function f(){if(l){var n="getAllResponseHeaders"in l?r(l.getAllResponseHeaders()):null,b={data:s&&"text"!==s&&"json"!==s?l.response:l.responseText,status:l.status,statusText:l.statusText,headers:n,config:t,request:l};p(e,o,b),l=null}}if(l.open(t.method.toUpperCase(),M(A,t.params,t.paramsSerializer),!0),l.timeout=t.timeout,"onloadend"in l?l.onloadend=f:l.onreadystatechange=function(){l&&4===l.readyState&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))&&setTimeout(f)},l.onabort=function(){l&&(o(i("Request aborted",t,"ECONNABORTED",l)),l=null)},l.onerror=function(){o(i("Network Error",t,null,l)),l=null},l.ontimeout=function(){var e="timeout of "+t.timeout+"ms exceeded";t.timeoutErrorMessage&&(e=t.timeoutErrorMessage),o(i(e,t,t.transitional&&t.transitional.clarifyTimeoutError?"ETIMEDOUT":"ECONNABORTED",l)),l=null},n.isStandardBrowserEnv()){var q=(t.withCredentials||c(A))&&t.xsrfCookieName?b.read(t.xsrfCookieName):void 0;q&&(O[t.xsrfHeaderName]=q)}"setRequestHeader"in l&&n.forEach(O,(function(t,e){void 0===a&&"content-type"===e.toLowerCase()?delete O[e]:l.setRequestHeader(e,t)})),n.isUndefined(t.withCredentials)||(l.withCredentials=!!t.withCredentials),s&&"json"!==s&&(l.responseType=t.responseType),"function"==typeof t.onDownloadProgress&&l.addEventListener("progress",t.onDownloadProgress),"function"==typeof t.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",t.onUploadProgress),t.cancelToken&&t.cancelToken.promise.then((function(t){l&&(l.abort(),o(t),l=null)})),a||(a=null),l.send(a)}))}},1609:(t,e,o)=>{"use strict";var n=o(4867),p=o(1849),b=o(321),M=o(7185);function z(t){var e=new b(t),o=p(b.prototype.request,e);return n.extend(o,b.prototype,e),n.extend(o,e),o}var r=z(o(5655));r.Axios=b,r.create=function(t){return z(M(r.defaults,t))},r.Cancel=o(5263),r.CancelToken=o(4972),r.isCancel=o(6502),r.all=function(t){return Promise.all(t)},r.spread=o(8713),r.isAxiosError=o(6268),t.exports=r,t.exports.default=r},5263:t=>{"use strict";function e(t){this.message=t}e.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},e.prototype.__CANCEL__=!0,t.exports=e},4972:(t,e,o)=>{"use strict";var n=o(5263);function p(t){if("function"!=typeof t)throw new TypeError("executor must be a function.");var e;this.promise=new Promise((function(t){e=t}));var o=this;t((function(t){o.reason||(o.reason=new n(t),e(o.reason))}))}p.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},p.source=function(){var t;return{token:new p((function(e){t=e})),cancel:t}},t.exports=p},6502:t=>{"use strict";t.exports=function(t){return!(!t||!t.__CANCEL__)}},321:(t,e,o)=>{"use strict";var n=o(4867),p=o(5327),b=o(782),M=o(3572),z=o(7185),r=o(4875),c=r.validators;function i(t){this.defaults=t,this.interceptors={request:new b,response:new b}}i.prototype.request=function(t){"string"==typeof t?(t=arguments[1]||{}).url=arguments[0]:t=t||{},(t=z(this.defaults,t)).method?t.method=t.method.toLowerCase():this.defaults.method?t.method=this.defaults.method.toLowerCase():t.method="get";var e=t.transitional;void 0!==e&&r.assertOptions(e,{silentJSONParsing:c.transitional(c.boolean,"1.0.0"),forcedJSONParsing:c.transitional(c.boolean,"1.0.0"),clarifyTimeoutError:c.transitional(c.boolean,"1.0.0")},!1);var o=[],n=!0;this.interceptors.request.forEach((function(e){"function"==typeof e.runWhen&&!1===e.runWhen(t)||(n=n&&e.synchronous,o.unshift(e.fulfilled,e.rejected))}));var p,b=[];if(this.interceptors.response.forEach((function(t){b.push(t.fulfilled,t.rejected)})),!n){var i=[M,void 0];for(Array.prototype.unshift.apply(i,o),i=i.concat(b),p=Promise.resolve(t);i.length;)p=p.then(i.shift(),i.shift());return p}for(var a=t;o.length;){var O=o.shift(),s=o.shift();try{a=O(a)}catch(t){s(t);break}}try{p=M(a)}catch(t){return Promise.reject(t)}for(;b.length;)p=p.then(b.shift(),b.shift());return p},i.prototype.getUri=function(t){return t=z(this.defaults,t),p(t.url,t.params,t.paramsSerializer).replace(/^\?/,"")},n.forEach(["delete","get","head","options"],(function(t){i.prototype[t]=function(e,o){return this.request(z(o||{},{method:t,url:e,data:(o||{}).data}))}})),n.forEach(["post","put","patch"],(function(t){i.prototype[t]=function(e,o,n){return this.request(z(n||{},{method:t,url:e,data:o}))}})),t.exports=i},782:(t,e,o)=>{"use strict";var n=o(4867);function p(){this.handlers=[]}p.prototype.use=function(t,e,o){return this.handlers.push({fulfilled:t,rejected:e,synchronous:!!o&&o.synchronous,runWhen:o?o.runWhen:null}),this.handlers.length-1},p.prototype.eject=function(t){this.handlers[t]&&(this.handlers[t]=null)},p.prototype.forEach=function(t){n.forEach(this.handlers,(function(e){null!==e&&t(e)}))},t.exports=p},4097:(t,e,o)=>{"use strict";var n=o(1793),p=o(7303);t.exports=function(t,e){return t&&!n(e)?p(t,e):e}},5061:(t,e,o)=>{"use strict";var n=o(481);t.exports=function(t,e,o,p,b){var M=new Error(t);return n(M,e,o,p,b)}},3572:(t,e,o)=>{"use strict";var n=o(4867),p=o(8527),b=o(6502),M=o(5655);function z(t){t.cancelToken&&t.cancelToken.throwIfRequested()}t.exports=function(t){return z(t),t.headers=t.headers||{},t.data=p.call(t,t.data,t.headers,t.transformRequest),t.headers=n.merge(t.headers.common||{},t.headers[t.method]||{},t.headers),n.forEach(["delete","get","head","post","put","patch","common"],(function(e){delete t.headers[e]})),(t.adapter||M.adapter)(t).then((function(e){return z(t),e.data=p.call(t,e.data,e.headers,t.transformResponse),e}),(function(e){return b(e)||(z(t),e&&e.response&&(e.response.data=p.call(t,e.response.data,e.response.headers,t.transformResponse))),Promise.reject(e)}))}},481:t=>{"use strict";t.exports=function(t,e,o,n,p){return t.config=e,o&&(t.code=o),t.request=n,t.response=p,t.isAxiosError=!0,t.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},t}},7185:(t,e,o)=>{"use strict";var n=o(4867);t.exports=function(t,e){e=e||{};var o={},p=["url","method","data"],b=["headers","auth","proxy","params"],M=["baseURL","transformRequest","transformResponse","paramsSerializer","timeout","timeoutMessage","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","decompress","maxContentLength","maxBodyLength","maxRedirects","transport","httpAgent","httpsAgent","cancelToken","socketPath","responseEncoding"],z=["validateStatus"];function r(t,e){return n.isPlainObject(t)&&n.isPlainObject(e)?n.merge(t,e):n.isPlainObject(e)?n.merge({},e):n.isArray(e)?e.slice():e}function c(p){n.isUndefined(e[p])?n.isUndefined(t[p])||(o[p]=r(void 0,t[p])):o[p]=r(t[p],e[p])}n.forEach(p,(function(t){n.isUndefined(e[t])||(o[t]=r(void 0,e[t]))})),n.forEach(b,c),n.forEach(M,(function(p){n.isUndefined(e[p])?n.isUndefined(t[p])||(o[p]=r(void 0,t[p])):o[p]=r(void 0,e[p])})),n.forEach(z,(function(n){n in e?o[n]=r(t[n],e[n]):n in t&&(o[n]=r(void 0,t[n]))}));var i=p.concat(b).concat(M).concat(z),a=Object.keys(t).concat(Object.keys(e)).filter((function(t){return-1===i.indexOf(t)}));return n.forEach(a,c),o}},6026:(t,e,o)=>{"use strict";var n=o(5061);t.exports=function(t,e,o){var p=o.config.validateStatus;o.status&&p&&!p(o.status)?e(n("Request failed with status code "+o.status,o.config,null,o.request,o)):t(o)}},8527:(t,e,o)=>{"use strict";var n=o(4867),p=o(5655);t.exports=function(t,e,o){var b=this||p;return n.forEach(o,(function(o){t=o.call(b,t,e)})),t}},5655:(t,e,o)=>{"use strict";var n=o(4155),p=o(4867),b=o(6016),M=o(481),z={"Content-Type":"application/x-www-form-urlencoded"};function r(t,e){!p.isUndefined(t)&&p.isUndefined(t["Content-Type"])&&(t["Content-Type"]=e)}var c,i={transitional:{silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1},adapter:(("undefined"!=typeof XMLHttpRequest||void 0!==n&&"[object process]"===Object.prototype.toString.call(n))&&(c=o(5448)),c),transformRequest:[function(t,e){return b(e,"Accept"),b(e,"Content-Type"),p.isFormData(t)||p.isArrayBuffer(t)||p.isBuffer(t)||p.isStream(t)||p.isFile(t)||p.isBlob(t)?t:p.isArrayBufferView(t)?t.buffer:p.isURLSearchParams(t)?(r(e,"application/x-www-form-urlencoded;charset=utf-8"),t.toString()):p.isObject(t)||e&&"application/json"===e["Content-Type"]?(r(e,"application/json"),function(t,e,o){if(p.isString(t))try{return(e||JSON.parse)(t),p.trim(t)}catch(t){if("SyntaxError"!==t.name)throw t}return(o||JSON.stringify)(t)}(t)):t}],transformResponse:[function(t){var e=this.transitional,o=e&&e.silentJSONParsing,n=e&&e.forcedJSONParsing,b=!o&&"json"===this.responseType;if(b||n&&p.isString(t)&&t.length)try{return JSON.parse(t)}catch(t){if(b){if("SyntaxError"===t.name)throw M(t,this,"E_JSON_PARSE");throw t}}return t}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,maxBodyLength:-1,validateStatus:function(t){return t>=200&&t<300}};i.headers={common:{Accept:"application/json, text/plain, */*"}},p.forEach(["delete","get","head"],(function(t){i.headers[t]={}})),p.forEach(["post","put","patch"],(function(t){i.headers[t]=p.merge(z)})),t.exports=i},1849:t=>{"use strict";t.exports=function(t,e){return function(){for(var o=new Array(arguments.length),n=0;n{"use strict";var n=o(4867);function p(t){return encodeURIComponent(t).replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}t.exports=function(t,e,o){if(!e)return t;var b;if(o)b=o(e);else if(n.isURLSearchParams(e))b=e.toString();else{var M=[];n.forEach(e,(function(t,e){null!=t&&(n.isArray(t)?e+="[]":t=[t],n.forEach(t,(function(t){n.isDate(t)?t=t.toISOString():n.isObject(t)&&(t=JSON.stringify(t)),M.push(p(e)+"="+p(t))})))})),b=M.join("&")}if(b){var z=t.indexOf("#");-1!==z&&(t=t.slice(0,z)),t+=(-1===t.indexOf("?")?"?":"&")+b}return t}},7303:t=>{"use strict";t.exports=function(t,e){return e?t.replace(/\/+$/,"")+"/"+e.replace(/^\/+/,""):t}},4372:(t,e,o)=>{"use strict";var n=o(4867);t.exports=n.isStandardBrowserEnv()?{write:function(t,e,o,p,b,M){var z=[];z.push(t+"="+encodeURIComponent(e)),n.isNumber(o)&&z.push("expires="+new Date(o).toGMTString()),n.isString(p)&&z.push("path="+p),n.isString(b)&&z.push("domain="+b),!0===M&&z.push("secure"),document.cookie=z.join("; ")},read:function(t){var e=document.cookie.match(new RegExp("(^|;\\s*)("+t+")=([^;]*)"));return e?decodeURIComponent(e[3]):null},remove:function(t){this.write(t,"",Date.now()-864e5)}}:{write:function(){},read:function(){return null},remove:function(){}}},1793:t=>{"use strict";t.exports=function(t){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(t)}},6268:t=>{"use strict";t.exports=function(t){return"object"==typeof t&&!0===t.isAxiosError}},7985:(t,e,o)=>{"use strict";var n=o(4867);t.exports=n.isStandardBrowserEnv()?function(){var t,e=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");function p(t){var n=t;return e&&(o.setAttribute("href",n),n=o.href),o.setAttribute("href",n),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}return t=p(window.location.href),function(e){var o=n.isString(e)?p(e):e;return o.protocol===t.protocol&&o.host===t.host}}():function(){return!0}},6016:(t,e,o)=>{"use strict";var n=o(4867);t.exports=function(t,e){n.forEach(t,(function(o,n){n!==e&&n.toUpperCase()===e.toUpperCase()&&(t[e]=o,delete t[n])}))}},4109:(t,e,o)=>{"use strict";var n=o(4867),p=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];t.exports=function(t){var e,o,b,M={};return t?(n.forEach(t.split("\n"),(function(t){if(b=t.indexOf(":"),e=n.trim(t.substr(0,b)).toLowerCase(),o=n.trim(t.substr(b+1)),e){if(M[e]&&p.indexOf(e)>=0)return;M[e]="set-cookie"===e?(M[e]?M[e]:[]).concat([o]):M[e]?M[e]+", "+o:o}})),M):M}},8713:t=>{"use strict";t.exports=function(t){return function(e){return t.apply(null,e)}}},4875:(t,e,o)=>{"use strict";var n=o(8593),p={};["object","boolean","number","function","string","symbol"].forEach((function(t,e){p[t]=function(o){return typeof o===t||"a"+(e<1?"n ":" ")+t}}));var b={},M=n.version.split(".");function z(t,e){for(var o=e?e.split("."):M,n=t.split("."),p=0;p<3;p++){if(o[p]>n[p])return!0;if(o[p]0;){var b=n[p],M=e[b];if(M){var z=t[b],r=void 0===z||M(z,b,t);if(!0!==r)throw new TypeError("option "+b+" must be "+r)}else if(!0!==o)throw Error("Unknown option "+b)}},validators:p}},4867:(t,e,o)=>{"use strict";var n=o(1849),p=Object.prototype.toString;function b(t){return"[object Array]"===p.call(t)}function M(t){return void 0===t}function z(t){return null!==t&&"object"==typeof t}function r(t){if("[object Object]"!==p.call(t))return!1;var e=Object.getPrototypeOf(t);return null===e||e===Object.prototype}function c(t){return"[object Function]"===p.call(t)}function i(t,e){if(null!=t)if("object"!=typeof t&&(t=[t]),b(t))for(var o=0,n=t.length;o{"use strict";var n=Object.freeze({}),p=Array.isArray;function b(t){return null==t}function M(t){return null!=t}function z(t){return!0===t}function r(t){return"string"==typeof t||"number"==typeof t||"symbol"==typeof t||"boolean"==typeof t}function c(t){return"function"==typeof t}function i(t){return null!==t&&"object"==typeof t}var a=Object.prototype.toString;function O(t){return"[object Object]"===a.call(t)}function s(t){return"[object RegExp]"===a.call(t)}function l(t){var e=parseFloat(String(t));return e>=0&&Math.floor(e)===e&&isFinite(t)}function d(t){return M(t)&&"function"==typeof t.then&&"function"==typeof t.catch}function u(t){return null==t?"":Array.isArray(t)||O(t)&&t.toString===a?JSON.stringify(t,null,2):String(t)}function A(t){var e=parseFloat(t);return isNaN(e)?t:e}function f(t,e){for(var o=Object.create(null),n=t.split(","),p=0;p-1)return t.splice(n,1)}}var v=Object.prototype.hasOwnProperty;function g(t,e){return v.call(t,e)}function m(t){var e=Object.create(null);return function(o){return e[o]||(e[o]=t(o))}}var R=/-(\w)/g,y=m((function(t){return t.replace(R,(function(t,e){return e?e.toUpperCase():""}))})),B=m((function(t){return t.charAt(0).toUpperCase()+t.slice(1)})),L=/\B([A-Z])/g,X=m((function(t){return t.replace(L,"-$1").toLowerCase()}));var _=Function.prototype.bind?function(t,e){return t.bind(e)}:function(t,e){function o(o){var n=arguments.length;return n?n>1?t.apply(e,arguments):t.call(e,o):t.call(e)}return o._length=t.length,o};function N(t,e){e=e||0;for(var o=t.length-e,n=new Array(o);o--;)n[o]=t[o+e];return n}function w(t,e){for(var o in e)t[o]=e[o];return t}function x(t){for(var e={},o=0;o0,et=Q&&Q.indexOf("edge/")>0;Q&&Q.indexOf("android");var ot=Q&&/iphone|ipad|ipod|ios/.test(Q);Q&&/chrome\/\d+/.test(Q),Q&&/phantomjs/.test(Q);var nt,pt=Q&&Q.match(/firefox\/(\d+)/),bt={}.watch,Mt=!1;if(K)try{var zt={};Object.defineProperty(zt,"passive",{get:function(){Mt=!0}}),window.addEventListener("test-passive",null,zt)}catch(t){}var rt=function(){return void 0===nt&&(nt=!K&&void 0!==o.g&&(o.g.process&&"server"===o.g.process.env.VUE_ENV)),nt},ct=K&&window.__VUE_DEVTOOLS_GLOBAL_HOOK__;function it(t){return"function"==typeof t&&/native code/.test(t.toString())}var at,Ot="undefined"!=typeof Symbol&&it(Symbol)&&"undefined"!=typeof Reflect&&it(Reflect.ownKeys);at="undefined"!=typeof Set&&it(Set)?Set:function(){function t(){this.set=Object.create(null)}return t.prototype.has=function(t){return!0===this.set[t]},t.prototype.add=function(t){this.set[t]=!0},t.prototype.clear=function(){this.set=Object.create(null)},t}();var st=null;function lt(t){void 0===t&&(t=null),t||st&&st._scope.off(),st=t,t&&t._scope.on()}var dt=function(){function t(t,e,o,n,p,b,M,z){this.tag=t,this.data=e,this.children=o,this.text=n,this.elm=p,this.ns=void 0,this.context=b,this.fnContext=void 0,this.fnOptions=void 0,this.fnScopeId=void 0,this.key=e&&e.key,this.componentOptions=M,this.componentInstance=void 0,this.parent=void 0,this.raw=!1,this.isStatic=!1,this.isRootInsert=!0,this.isComment=!1,this.isCloned=!1,this.isOnce=!1,this.asyncFactory=z,this.asyncMeta=void 0,this.isAsyncPlaceholder=!1}return Object.defineProperty(t.prototype,"child",{get:function(){return this.componentInstance},enumerable:!1,configurable:!0}),t}(),ut=function(t){void 0===t&&(t="");var e=new dt;return e.text=t,e.isComment=!0,e};function At(t){return new dt(void 0,void 0,void 0,String(t))}function ft(t){var e=new dt(t.tag,t.data,t.children&&t.children.slice(),t.text,t.elm,t.context,t.componentOptions,t.asyncFactory);return e.ns=t.ns,e.isStatic=t.isStatic,e.key=t.key,e.isComment=t.isComment,e.fnContext=t.fnContext,e.fnOptions=t.fnOptions,e.fnScopeId=t.fnScopeId,e.asyncMeta=t.asyncMeta,e.isCloned=!0,e}var qt=0,ht=[],Wt=function(){function t(){this._pending=!1,this.id=qt++,this.subs=[]}return t.prototype.addSub=function(t){this.subs.push(t)},t.prototype.removeSub=function(t){this.subs[this.subs.indexOf(t)]=null,this._pending||(this._pending=!0,ht.push(this))},t.prototype.depend=function(e){t.target&&t.target.addDep(this)},t.prototype.notify=function(t){var e=this.subs.filter((function(t){return t}));for(var o=0,n=e.length;o0&&(Jt((n=Kt(n,"".concat(e||"","_").concat(o)))[0])&&Jt(i)&&(a[c]=At(i.text+n[0].text),n.shift()),a.push.apply(a,n)):r(n)?Jt(i)?a[c]=At(i.text+n):""!==n&&a.push(At(n)):Jt(n)&&Jt(i)?a[c]=At(i.text+n.text):(z(t._isVList)&&M(n.tag)&&b(n.key)&&M(e)&&(n.key="__vlist".concat(e,"_").concat(o,"__")),a.push(n)));return a}function Qt(t,e,o,n,b,a){return(p(o)||r(o))&&(b=n,n=o,o=void 0),z(a)&&(b=2),function(t,e,o,n,b){if(M(o)&&M(o.__ob__))return ut();M(o)&&M(o.is)&&(e=o.is);if(!e)return ut();0;p(n)&&c(n[0])&&((o=o||{}).scopedSlots={default:n[0]},n.length=0);2===b?n=Gt(n):1===b&&(n=function(t){for(var e=0;e0,z=e?!!e.$stable:!M,r=e&&e.$key;if(e){if(e._normalized)return e._normalized;if(z&&p&&p!==n&&r===p.$key&&!M&&!p.$hasNormal)return p;for(var c in b={},e)e[c]&&"$"!==c[0]&&(b[c]=qe(t,o,c,e[c]))}else b={};for(var i in o)i in b||(b[i]=he(o,i));return e&&Object.isExtensible(e)&&(e._normalized=b),Y(b,"$stable",z),Y(b,"$key",r),Y(b,"$hasNormal",M),b}function qe(t,e,o,n){var b=function(){var e=st;lt(t);var o=arguments.length?n.apply(null,arguments):n({}),b=(o=o&&"object"==typeof o&&!p(o)?[o]:Gt(o))&&o[0];return lt(e),o&&(!b||1===o.length&&b.isComment&&!Ae(b))?void 0:o};return n.proxy&&Object.defineProperty(e,o,{get:b,enumerable:!0,configurable:!0}),b}function he(t,e){return function(){return t[e]}}function We(t){return{get attrs(){if(!t._attrsProxy){var e=t._attrsProxy={};Y(e,"_v_attr_proxy",!0),ve(e,t.$attrs,n,t,"$attrs")}return t._attrsProxy},get listeners(){t._listenersProxy||ve(t._listenersProxy={},t.$listeners,n,t,"$listeners");return t._listenersProxy},get slots(){return function(t){t._slotsProxy||me(t._slotsProxy={},t.$scopedSlots);return t._slotsProxy}(t)},emit:_(t.$emit,t),expose:function(e){e&&Object.keys(e).forEach((function(o){return Ft(t,e,o)}))}}}function ve(t,e,o,n,p){var b=!1;for(var M in e)M in t?e[M]!==o[M]&&(b=!0):(b=!0,ge(t,M,n,p));for(var M in t)M in e||(b=!0,delete t[M]);return b}function ge(t,e,o,n){Object.defineProperty(t,e,{enumerable:!0,configurable:!0,get:function(){return o[n][e]}})}function me(t,e){for(var o in e)t[o]=e[o];for(var o in t)o in e||delete t[o]}var Re,ye=null;function Be(t,e){return(t.__esModule||Ot&&"Module"===t[Symbol.toStringTag])&&(t=t.default),i(t)?e.extend(t):t}function Le(t){if(p(t))for(var e=0;edocument.createEvent("Event").timeStamp&&(Ve=function(){return $e.now()})}var Ye=function(t,e){if(t.post){if(!e.post)return 1}else if(e.post)return-1;return t.id-e.id};function Ge(){var t,e;for(Ue=Ve(),Fe=!0,De.sort(Ye),He=0;HeHe&&De[o].id>t.id;)o--;De.splice(o+1,0,t)}else De.push(t);Ie||(Ie=!0,lo(Ge))}}var Ke="watcher";"".concat(Ke," callback"),"".concat(Ke," getter"),"".concat(Ke," cleanup");var Qe;var Ze=function(){function t(t){void 0===t&&(t=!1),this.detached=t,this.active=!0,this.effects=[],this.cleanups=[],this.parent=Qe,!t&&Qe&&(this.index=(Qe.scopes||(Qe.scopes=[])).push(this)-1)}return t.prototype.run=function(t){if(this.active){var e=Qe;try{return Qe=this,t()}finally{Qe=e}}else 0},t.prototype.on=function(){Qe=this},t.prototype.off=function(){Qe=this.parent},t.prototype.stop=function(t){if(this.active){var e=void 0,o=void 0;for(e=0,o=this.effects.length;e-1)if(b&&!g(p,"default"))M=!1;else if(""===M||M===X(t)){var r=tn(String,p.type);(r<0||z-1:"string"==typeof t?t.split(",").indexOf(e)>-1:!!s(t)&&t.test(e)}function bn(t,e){var o=t.cache,n=t.keys,p=t._vnode;for(var b in o){var M=o[b];if(M){var z=M.name;z&&!e(z)&&Mn(o,b,n,p)}}}function Mn(t,e,o,n){var p=t[e];!p||n&&p.tag===n.tag||p.componentInstance.$destroy(),t[e]=null,W(o,e)}!function(t){t.prototype._init=function(t){var e=this;e._uid=No++,e._isVue=!0,e.__v_skip=!0,e._scope=new Ze(!0),e._scope._vm=!0,t&&t._isComponent?function(t,e){var o=t.$options=Object.create(t.constructor.options),n=e._parentVnode;o.parent=e.parent,o._parentVnode=n;var p=n.componentOptions;o.propsData=p.propsData,o._parentListeners=p.listeners,o._renderChildren=p.children,o._componentTag=p.tag,e.render&&(o.render=e.render,o.staticRenderFns=e.staticRenderFns)}(e,t):e.$options=Yo(wo(e.constructor),t||{},e),e._renderProxy=e,e._self=e,function(t){var e=t.$options,o=e.parent;if(o&&!e.abstract){for(;o.$options.abstract&&o.$parent;)o=o.$parent;o.$children.push(t)}t.$parent=o,t.$root=o?o.$root:t,t.$children=[],t.$refs={},t._provided=o?o._provided:Object.create(null),t._watcher=null,t._inactive=null,t._directInactive=!1,t._isMounted=!1,t._isDestroyed=!1,t._isBeingDestroyed=!1}(e),function(t){t._events=Object.create(null),t._hasHookEvent=!1;var e=t.$options._parentListeners;e&&we(t,e)}(e),function(t){t._vnode=null,t._staticTrees=null;var e=t.$options,o=t.$vnode=e._parentVnode,p=o&&o.context;t.$slots=de(e._renderChildren,p),t.$scopedSlots=o?fe(t.$parent,o.data.scopedSlots,t.$slots):n,t._c=function(e,o,n,p){return Qt(t,e,o,n,p,!1)},t.$createElement=function(e,o,n,p){return Qt(t,e,o,n,p,!0)};var b=o&&o.data;Et(t,"$attrs",b&&b.attrs||n,null,!0),Et(t,"$listeners",e._parentListeners||n,null,!0)}(e),Ee(e,"beforeCreate",void 0,!1),function(t){var e=_o(t.$options.inject,t);e&&(Tt(!1),Object.keys(e).forEach((function(o){Et(t,o,e[o])})),Tt(!0))}(e),mo(e),function(t){var e=t.$options.provide;if(e){var o=c(e)?e.call(t):e;if(!i(o))return;for(var n=to(t),p=Ot?Reflect.ownKeys(o):Object.keys(o),b=0;b1?N(o):o;for(var n=N(arguments,1),p='event handler for "'.concat(t,'"'),b=0,M=o.length;bparseInt(this.max)&&Mn(e,o[0],o,this._vnode),this.vnodeToCache=null}}},created:function(){this.cache=Object.create(null),this.keys=[]},destroyed:function(){for(var t in this.cache)Mn(this.cache,t,this.keys)},mounted:function(){var t=this;this.cacheVNode(),this.$watch("include",(function(e){bn(t,(function(t){return pn(e,t)}))})),this.$watch("exclude",(function(e){bn(t,(function(t){return!pn(e,t)}))}))},updated:function(){this.cacheVNode()},render:function(){var t=this.$slots.default,e=Le(t),o=e&&e.componentOptions;if(o){var n=nn(o),p=this.include,b=this.exclude;if(p&&(!n||!pn(p,n))||b&&n&&pn(b,n))return e;var M=this.cache,z=this.keys,r=null==e.key?o.Ctor.cid+(o.tag?"::".concat(o.tag):""):e.key;M[r]?(e.componentInstance=M[r].componentInstance,W(z,r),z.push(r)):(this.vnodeToCache=e,this.keyToCache=r),e.data.keepAlive=!0}return e||t&&t[0]}},cn={KeepAlive:rn};!function(t){var e={get:function(){return H}};Object.defineProperty(t,"config",e),t.util={warn:jo,extend:w,mergeOptions:Yo,defineReactive:Et},t.set=Dt,t.delete=Pt,t.nextTick=lo,t.observable=function(t){return kt(t),t},t.options=Object.create(null),I.forEach((function(e){t.options[e+"s"]=Object.create(null)})),t.options._base=t,w(t.options.components,cn),function(t){t.use=function(t){var e=this._installedPlugins||(this._installedPlugins=[]);if(e.indexOf(t)>-1)return this;var o=N(arguments,1);return o.unshift(this),c(t.install)?t.install.apply(t,o):c(t)&&t.apply(null,o),e.push(t),this}}(t),function(t){t.mixin=function(t){return this.options=Yo(this.options,t),this}}(t),on(t),function(t){I.forEach((function(e){t[e]=function(t,o){return o?("component"===e&&O(o)&&(o.name=o.name||t,o=this.options._base.extend(o)),"directive"===e&&c(o)&&(o={bind:o,update:o}),this.options[e+"s"][t]=o,o):this.options[e+"s"][t]}}))}(t)}(en),Object.defineProperty(en.prototype,"$isServer",{get:rt}),Object.defineProperty(en.prototype,"$ssrContext",{get:function(){return this.$vnode&&this.$vnode.ssrContext}}),Object.defineProperty(en,"FunctionalRenderContext",{value:xo}),en.version="2.7.13";var an=f("style,class"),On=f("input,textarea,option,select,progress"),sn=function(t,e,o){return"value"===o&&On(t)&&"button"!==e||"selected"===o&&"option"===t||"checked"===o&&"input"===t||"muted"===o&&"video"===t},ln=f("contenteditable,draggable,spellcheck"),dn=f("events,caret,typing,plaintext-only"),un=f("allowfullscreen,async,autofocus,autoplay,checked,compact,controls,declare,default,defaultchecked,defaultmuted,defaultselected,defer,disabled,enabled,formnovalidate,hidden,indeterminate,inert,ismap,itemscope,loop,multiple,muted,nohref,noresize,noshade,novalidate,nowrap,open,pauseonexit,readonly,required,reversed,scoped,seamless,selected,sortable,truespeed,typemustmatch,visible"),An="http://www.w3.org/1999/xlink",fn=function(t){return":"===t.charAt(5)&&"xlink"===t.slice(0,5)},qn=function(t){return fn(t)?t.slice(6,t.length):""},hn=function(t){return null==t||!1===t};function Wn(t){for(var e=t.data,o=t,n=t;M(n.componentInstance);)(n=n.componentInstance._vnode)&&n.data&&(e=vn(n.data,e));for(;M(o=o.parent);)o&&o.data&&(e=vn(e,o.data));return function(t,e){if(M(t)||M(e))return gn(t,mn(e));return""}(e.staticClass,e.class)}function vn(t,e){return{staticClass:gn(t.staticClass,e.staticClass),class:M(t.class)?[t.class,e.class]:e.class}}function gn(t,e){return t?e?t+" "+e:t:e||""}function mn(t){return Array.isArray(t)?function(t){for(var e,o="",n=0,p=t.length;n-1?Jn(t,e,o):un(e)?hn(o)?t.removeAttribute(e):(o="allowfullscreen"===e&&"EMBED"===t.tagName?"true":e,t.setAttribute(e,o)):ln(e)?t.setAttribute(e,function(t,e){return hn(e)||"false"===e?"false":"contenteditable"===t&&dn(e)?e:"true"}(e,o)):fn(e)?hn(o)?t.removeAttributeNS(An,qn(e)):t.setAttributeNS(An,e,o):Jn(t,e,o)}function Jn(t,e,o){if(hn(o))t.removeAttribute(e);else{if(Z&&!tt&&"TEXTAREA"===t.tagName&&"placeholder"===e&&""!==o&&!t.__ieph){var n=function(e){e.stopImmediatePropagation(),t.removeEventListener("input",n)};t.addEventListener("input",n),t.__ieph=!0}t.setAttribute(e,o)}}var Kn={create:Yn,update:Yn};function Qn(t,e){var o=e.elm,n=e.data,p=t.data;if(!(b(n.staticClass)&&b(n.class)&&(b(p)||b(p.staticClass)&&b(p.class)))){var z=Wn(e),r=o._transitionClasses;M(r)&&(z=gn(z,mn(r))),z!==o._prevClass&&(o.setAttribute("class",z),o._prevClass=z)}}var Zn,tp,ep,op,np,pp,bp={create:Qn,update:Qn},Mp=/[\w).+\-_$\]]/;function zp(t){var e,o,n,p,b,M=!1,z=!1,r=!1,c=!1,i=0,a=0,O=0,s=0;for(n=0;n=0&&" "===(d=t.charAt(l));l--);d&&Mp.test(d)||(c=!0)}}else void 0===p?(s=n+1,p=t.slice(0,n).trim()):u();function u(){(b||(b=[])).push(t.slice(s,n).trim()),s=n+1}if(void 0===p?p=t.slice(0,n).trim():0!==s&&u(),b)for(n=0;n-1?{exp:t.slice(0,op),key:'"'+t.slice(op+1)+'"'}:{exp:t,key:null};tp=t,op=np=pp=0;for(;!mp();)Rp(ep=gp())?Bp(ep):91===ep&&yp(ep);return{exp:t.slice(0,np),key:t.slice(np+1,pp)}}(t);return null===o.key?"".concat(t,"=").concat(e):"$set(".concat(o.exp,", ").concat(o.key,", ").concat(e,")")}function gp(){return tp.charCodeAt(++op)}function mp(){return op>=Zn}function Rp(t){return 34===t||39===t}function yp(t){var e=1;for(np=op;!mp();)if(Rp(t=gp()))Bp(t);else if(91===t&&e++,93===t&&e--,0===e){pp=op;break}}function Bp(t){for(var e=t;!mp()&&(t=gp())!==e;);}var Lp,Xp="__r";function _p(t,e,o){var n=Lp;return function p(){var b=e.apply(null,arguments);null!==b&&xp(t,p,o,n)}}var Np=Mo&&!(pt&&Number(pt[1])<=53);function wp(t,e,o,n){if(Np){var p=Ue,b=e;e=b._wrapper=function(t){if(t.target===t.currentTarget||t.timeStamp>=p||t.timeStamp<=0||t.target.ownerDocument!==document)return b.apply(this,arguments)}}Lp.addEventListener(t,e,Mt?{capture:o,passive:n}:o)}function xp(t,e,o,n){(n||Lp).removeEventListener(t,e._wrapper||e,o)}function Tp(t,e){if(!b(t.data.on)||!b(e.data.on)){var o=e.data.on||{},n=t.data.on||{};Lp=e.elm||t.elm,function(t){if(M(t.__r)){var e=Z?"change":"input";t[e]=[].concat(t.__r,t[e]||[]),delete t.__r}M(t.__c)&&(t.change=[].concat(t.__c,t.change||[]),delete t.__c)}(o),Vt(o,n,wp,xp,_p,e.context),Lp=void 0}}var Cp,Sp={create:Tp,update:Tp,destroy:function(t){return Tp(t,kn)}};function kp(t,e){if(!b(t.data.domProps)||!b(e.data.domProps)){var o,n,p=e.elm,r=t.data.domProps||{},c=e.data.domProps||{};for(o in(M(c.__ob__)||z(c._v_attr_proxy))&&(c=e.data.domProps=w({},c)),r)o in c||(p[o]="");for(o in c){if(n=c[o],"textContent"===o||"innerHTML"===o){if(e.children&&(e.children.length=0),n===r[o])continue;1===p.childNodes.length&&p.removeChild(p.childNodes[0])}if("value"===o&&"PROGRESS"!==p.tagName){p._value=n;var i=b(n)?"":String(n);Ep(p,i)&&(p.value=i)}else if("innerHTML"===o&&Bn(p.tagName)&&b(p.innerHTML)){(Cp=Cp||document.createElement("div")).innerHTML="".concat(n,"");for(var a=Cp.firstChild;p.firstChild;)p.removeChild(p.firstChild);for(;a.firstChild;)p.appendChild(a.firstChild)}else if(n!==r[o])try{p[o]=n}catch(t){}}}}function Ep(t,e){return!t.composing&&("OPTION"===t.tagName||function(t,e){var o=!0;try{o=document.activeElement!==t}catch(t){}return o&&t.value!==e}(t,e)||function(t,e){var o=t.value,n=t._vModifiers;if(M(n)){if(n.number)return A(o)!==A(e);if(n.trim)return o.trim()!==e.trim()}return o!==e}(t,e))}var Dp={create:kp,update:kp},Pp=m((function(t){var e={},o=/:(.+)/;return t.split(/;(?![^(]*\))/g).forEach((function(t){if(t){var n=t.split(o);n.length>1&&(e[n[0].trim()]=n[1].trim())}})),e}));function jp(t){var e=Ip(t.style);return t.staticStyle?w(t.staticStyle,e):e}function Ip(t){return Array.isArray(t)?x(t):"string"==typeof t?Pp(t):t}var Fp,Hp=/^--/,Up=/\s*!important$/,Vp=function(t,e,o){if(Hp.test(e))t.style.setProperty(e,o);else if(Up.test(o))t.style.setProperty(X(e),o.replace(Up,""),"important");else{var n=Yp(e);if(Array.isArray(o))for(var p=0,b=o.length;p-1?e.split(Kp).forEach((function(e){return t.classList.add(e)})):t.classList.add(e);else{var o=" ".concat(t.getAttribute("class")||""," ");o.indexOf(" "+e+" ")<0&&t.setAttribute("class",(o+e).trim())}}function Zp(t,e){if(e&&(e=e.trim()))if(t.classList)e.indexOf(" ")>-1?e.split(Kp).forEach((function(e){return t.classList.remove(e)})):t.classList.remove(e),t.classList.length||t.removeAttribute("class");else{for(var o=" ".concat(t.getAttribute("class")||""," "),n=" "+e+" ";o.indexOf(n)>=0;)o=o.replace(n," ");(o=o.trim())?t.setAttribute("class",o):t.removeAttribute("class")}}function tb(t){if(t){if("object"==typeof t){var e={};return!1!==t.css&&w(e,eb(t.name||"v")),w(e,t),e}return"string"==typeof t?eb(t):void 0}}var eb=m((function(t){return{enterClass:"".concat(t,"-enter"),enterToClass:"".concat(t,"-enter-to"),enterActiveClass:"".concat(t,"-enter-active"),leaveClass:"".concat(t,"-leave"),leaveToClass:"".concat(t,"-leave-to"),leaveActiveClass:"".concat(t,"-leave-active")}})),ob=K&&!tt,nb="transition",pb="animation",bb="transition",Mb="transitionend",zb="animation",rb="animationend";ob&&(void 0===window.ontransitionend&&void 0!==window.onwebkittransitionend&&(bb="WebkitTransition",Mb="webkitTransitionEnd"),void 0===window.onanimationend&&void 0!==window.onwebkitanimationend&&(zb="WebkitAnimation",rb="webkitAnimationEnd"));var cb=K?window.requestAnimationFrame?window.requestAnimationFrame.bind(window):setTimeout:function(t){return t()};function ib(t){cb((function(){cb(t)}))}function ab(t,e){var o=t._transitionClasses||(t._transitionClasses=[]);o.indexOf(e)<0&&(o.push(e),Qp(t,e))}function Ob(t,e){t._transitionClasses&&W(t._transitionClasses,e),Zp(t,e)}function sb(t,e,o){var n=db(t,e),p=n.type,b=n.timeout,M=n.propCount;if(!p)return o();var z=p===nb?Mb:rb,r=0,c=function(){t.removeEventListener(z,i),o()},i=function(e){e.target===t&&++r>=M&&c()};setTimeout((function(){r0&&(o=nb,i=M,a=b.length):e===pb?c>0&&(o=pb,i=c,a=r.length):a=(o=(i=Math.max(M,c))>0?M>c?nb:pb:null)?o===nb?b.length:r.length:0,{type:o,timeout:i,propCount:a,hasTransform:o===nb&&lb.test(n[bb+"Property"])}}function ub(t,e){for(;t.length1}function vb(t,e){!0!==e.data.show&&fb(e)}var gb=function(t){var e,o,n={},c=t.modules,i=t.nodeOps;for(e=0;el?h(t,b(o[A+1])?null:o[A+1].elm,o,s,A,n):s>A&&v(e,a,l)}(a,d,A,o,c):M(A)?(M(t.text)&&i.setTextContent(a,""),h(a,null,A,0,A.length-1,o)):M(d)?v(d,0,d.length-1):M(t.text)&&i.setTextContent(a,""):t.text!==e.text&&i.setTextContent(a,e.text),M(l)&&M(s=l.hook)&&M(s=s.postpatch)&&s(t,e)}}}function y(t,e,o){if(z(o)&&M(t.parent))t.parent.data.pendingInsert=e;else for(var n=0;n-1,M.selected!==b&&(M.selected=b);else if(k(Lb(M),n))return void(t.selectedIndex!==z&&(t.selectedIndex=z));p||(t.selectedIndex=-1)}}function Bb(t,e){return e.every((function(e){return!k(e,t)}))}function Lb(t){return"_value"in t?t._value:t.value}function Xb(t){t.target.composing=!0}function _b(t){t.target.composing&&(t.target.composing=!1,Nb(t.target,"input"))}function Nb(t,e){var o=document.createEvent("HTMLEvents");o.initEvent(e,!0,!0),t.dispatchEvent(o)}function wb(t){return!t.componentInstance||t.data&&t.data.transition?t:wb(t.componentInstance._vnode)}var xb={bind:function(t,e,o){var n=e.value,p=(o=wb(o)).data&&o.data.transition,b=t.__vOriginalDisplay="none"===t.style.display?"":t.style.display;n&&p?(o.data.show=!0,fb(o,(function(){t.style.display=b}))):t.style.display=n?b:"none"},update:function(t,e,o){var n=e.value;!n!=!e.oldValue&&((o=wb(o)).data&&o.data.transition?(o.data.show=!0,n?fb(o,(function(){t.style.display=t.__vOriginalDisplay})):qb(o,(function(){t.style.display="none"}))):t.style.display=n?t.__vOriginalDisplay:"none")},unbind:function(t,e,o,n,p){p||(t.style.display=t.__vOriginalDisplay)}},Tb={model:mb,show:xb},Cb={name:String,appear:Boolean,css:Boolean,mode:String,type:String,enterClass:String,leaveClass:String,enterToClass:String,leaveToClass:String,enterActiveClass:String,leaveActiveClass:String,appearClass:String,appearActiveClass:String,appearToClass:String,duration:[Number,String,Object]};function Sb(t){var e=t&&t.componentOptions;return e&&e.Ctor.options.abstract?Sb(Le(e.children)):t}function kb(t){var e={},o=t.$options;for(var n in o.propsData)e[n]=t[n];var p=o._parentListeners;for(var n in p)e[y(n)]=p[n];return e}function Eb(t,e){if(/\d-keep-alive$/.test(e.tag))return t("keep-alive",{props:e.componentOptions.propsData})}var Db=function(t){return t.tag||Ae(t)},Pb=function(t){return"show"===t.name},jb={name:"transition",props:Cb,abstract:!0,render:function(t){var e=this,o=this.$slots.default;if(o&&(o=o.filter(Db)).length){0;var n=this.mode;0;var p=o[0];if(function(t){for(;t=t.parent;)if(t.data.transition)return!0}(this.$vnode))return p;var b=Sb(p);if(!b)return p;if(this._leaving)return Eb(t,p);var M="__transition-".concat(this._uid,"-");b.key=null==b.key?b.isComment?M+"comment":M+b.tag:r(b.key)?0===String(b.key).indexOf(M)?b.key:M+b.key:b.key;var z=(b.data||(b.data={})).transition=kb(this),c=this._vnode,i=Sb(c);if(b.data.directives&&b.data.directives.some(Pb)&&(b.data.show=!0),i&&i.data&&!function(t,e){return e.key===t.key&&e.tag===t.tag}(b,i)&&!Ae(i)&&(!i.componentInstance||!i.componentInstance._vnode.isComment)){var a=i.data.transition=w({},z);if("out-in"===n)return this._leaving=!0,$t(a,"afterLeave",(function(){e._leaving=!1,e.$forceUpdate()})),Eb(t,p);if("in-out"===n){if(Ae(b))return c;var O,s=function(){O()};$t(z,"afterEnter",s),$t(z,"enterCancelled",s),$t(a,"delayLeave",(function(t){O=t}))}}return p}}},Ib=w({tag:String,moveClass:String},Cb);delete Ib.mode;var Fb={props:Ib,beforeMount:function(){var t=this,e=this._update;this._update=function(o,n){var p=Te(t);t.__patch__(t._vnode,t.kept,!1,!0),t._vnode=t.kept,p(),e.call(t,o,n)}},render:function(t){for(var e=this.tag||this.$vnode.data.tag||"span",o=Object.create(null),n=this.prevChildren=this.children,p=this.$slots.default||[],b=this.children=[],M=kb(this),z=0;z-1?_n[t]=e.constructor===window.HTMLUnknownElement||e.constructor===window.HTMLElement:_n[t]=/HTMLUnknownElement/.test(e.toString())},w(en.options.directives,Tb),w(en.options.components,$b),en.prototype.__patch__=K?gb:T,en.prototype.$mount=function(t,e){return function(t,e,o){var n;t.$el=e,t.$options.render||(t.$options.render=ut),Ee(t,"beforeMount"),n=function(){t._update(t._render(),o)},new Wo(t,n,T,{before:function(){t._isMounted&&!t._isDestroyed&&Ee(t,"beforeUpdate")}},!0),o=!1;var p=t._preWatchers;if(p)for(var b=0;b\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,bM=/^\s*((?:v-[\w-]+:|@|:|#)\[[^=]+?\][^\s"'<>\/=]*)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/,MM="[a-zA-Z_][\\-\\.0-9_a-zA-Z".concat(U.source,"]*"),zM="((?:".concat(MM,"\\:)?").concat(MM,")"),rM=new RegExp("^<".concat(zM)),cM=/^\s*(\/?)>/,iM=new RegExp("^<\\/".concat(zM,"[^>]*>")),aM=/^]+>/i,OM=/^",""":'"',"&":"&"," ":"\n"," ":"\t","'":"'"},AM=/&(?:lt|gt|quot|amp|#39);/g,fM=/&(?:lt|gt|quot|amp|#39|#10|#9);/g,qM=f("pre,textarea",!0),hM=function(t,e){return t&&qM(t)&&"\n"===e[0]};function WM(t,e){var o=e?fM:AM;return t.replace(o,(function(t){return uM[t]}))}function vM(t,e){for(var o,n,p=[],b=e.expectHTML,M=e.isUnaryTag||C,z=e.canBeLeftOpenTag||C,r=0,c=function(){if(o=t,n&&lM(n)){var c=0,O=n.toLowerCase(),s=dM[O]||(dM[O]=new RegExp("([\\s\\S]*?)(]*>)","i"));v=t.replace(s,(function(t,o,n){return c=n.length,lM(O)||"noscript"===O||(o=o.replace(//g,"$1").replace(//g,"$1")),hM(O,o)&&(o=o.slice(1)),e.chars&&e.chars(o),""}));r+=t.length-v.length,t=v,a(O,r-c,r)}else{var l=t.indexOf("<");if(0===l){if(OM.test(t)){var d=t.indexOf("--\x3e");if(d>=0)return e.shouldKeepComment&&e.comment&&e.comment(t.substring(4,d),r,r+d+3),i(d+3),"continue"}if(sM.test(t)){var u=t.indexOf("]>");if(u>=0)return i(u+2),"continue"}var A=t.match(aM);if(A)return i(A[0].length),"continue";var f=t.match(iM);if(f){var q=r;return i(f[0].length),a(f[1],q,r),"continue"}var h=function(){var e=t.match(rM);if(e){var o={tagName:e[1],attrs:[],start:r};i(e[0].length);for(var n=void 0,p=void 0;!(n=t.match(cM))&&(p=t.match(bM)||t.match(pM));)p.start=r,i(p[0].length),p.end=r,o.attrs.push(p);if(n)return o.unarySlash=n[1],i(n[0].length),o.end=r,o}}();if(h)return function(t){var o=t.tagName,r=t.unarySlash;b&&("p"===n&&nM(o)&&a(n),z(o)&&n===o&&a(o));for(var c=M(o)||!!r,i=t.attrs.length,O=new Array(i),s=0;s=0){for(v=t.slice(l);!(iM.test(v)||rM.test(v)||OM.test(v)||sM.test(v)||(g=v.indexOf("<",1))<0);)l+=g,v=t.slice(l);W=t.substring(0,l)}l<0&&(W=t),W&&i(W.length),e.chars&&W&&e.chars(W,r-W.length,r)}if(t===o)return e.chars&&e.chars(t),"break"};t;){if("break"===c())break}function i(e){r+=e,t=t.substring(e)}function a(t,o,b){var M,z;if(null==o&&(o=r),null==b&&(b=r),t)for(z=t.toLowerCase(),M=p.length-1;M>=0&&p[M].lowerCasedTag!==z;M--);else M=0;if(M>=0){for(var c=p.length-1;c>=M;c--)e.end&&e.end(p[c].tag,o,b);p.length=M,n=M&&p[M-1].tag}else"br"===z?e.start&&e.start(t,[],!0,o,b):"p"===z&&(e.start&&e.start(t,[],!1,o,b),e.end&&e.end(t,o,b))}a()}var gM,mM,RM,yM,BM,LM,XM,_M,NM=/^@|^v-on:/,wM=/^v-|^@|^:|^#/,xM=/([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/,TM=/,([^,\}\]]*)(?:,([^,\}\]]*))?$/,CM=/^\(|\)$/g,SM=/^\[.*\]$/,kM=/:(.*)$/,EM=/^:|^\.|^v-bind:/,DM=/\.[^.\]]+(?=[^\]]*$)/g,PM=/^v-slot(:|$)|^#/,jM=/[\r\n]/,IM=/[ \f\t\r\n]+/g,FM=m(tM),HM="_empty_";function UM(t,e,o){return{type:1,tag:t,attrsList:e,attrsMap:QM(e),rawAttrsMap:{},parent:o,children:[]}}function VM(t,e){gM=e.warn||cp,LM=e.isPreTag||C,XM=e.mustUseProp||C,_M=e.getTagNamespace||C;var o=e.isReservedTag||C;(function(t){return!(!(t.component||t.attrsMap[":is"]||t.attrsMap["v-bind:is"])&&(t.attrsMap.is?o(t.attrsMap.is):o(t.tag)))}),RM=ip(e.modules,"transformNode"),yM=ip(e.modules,"preTransformNode"),BM=ip(e.modules,"postTransformNode"),mM=e.delimiters;var n,p,b=[],M=!1!==e.preserveWhitespace,z=e.whitespace,r=!1,c=!1;function i(t){if(a(t),r||t.processed||(t=$M(t,e)),b.length||t===n||n.if&&(t.elseif||t.else)&&GM(n,{exp:t.elseif,block:t}),p&&!t.forbidden)if(t.elseif||t.else)M=t,z=function(t){for(var e=t.length;e--;){if(1===t[e].type)return t[e];t.pop()}}(p.children),z&&z.if&&GM(z,{exp:M.elseif,block:M});else{if(t.slotScope){var o=t.slotTarget||'"default"';(p.scopedSlots||(p.scopedSlots={}))[o]=t}p.children.push(t),t.parent=p}var M,z;t.children=t.children.filter((function(t){return!t.slotScope})),a(t),t.pre&&(r=!1),LM(t.tag)&&(c=!1);for(var i=0;ir&&(z.push(b=t.slice(r,p)),M.push(JSON.stringify(b)));var c=zp(n[1].trim());M.push("_s(".concat(c,")")),z.push({"@binding":c}),r=p+n[0].length}return r-1")+("true"===b?":(".concat(e,")"):":_q(".concat(e,",").concat(b,")"))),up(t,"change","var $$a=".concat(e,",")+"$$el=$event.target,"+"$$c=$$el.checked?(".concat(b,"):(").concat(M,");")+"if(Array.isArray($$a)){"+"var $$v=".concat(n?"_n("+p+")":p,",")+"$$i=_i($$a,$$v);"+"if($$el.checked){$$i<0&&(".concat(vp(e,"$$a.concat([$$v])"),")}")+"else{$$i>-1&&(".concat(vp(e,"$$a.slice(0,$$i).concat($$a.slice($$i+1))"),")}")+"}else{".concat(vp(e,"$$c"),"}"),null,!0)}(t,n,p);else if("input"===b&&"radio"===M)!function(t,e,o){var n=o&&o.number,p=Ap(t,"value")||"null";p=n?"_n(".concat(p,")"):p,ap(t,"checked","_q(".concat(e,",").concat(p,")")),up(t,"change",vp(e,p),null,!0)}(t,n,p);else if("input"===b||"textarea"===b)!function(t,e,o){var n=t.attrsMap.type;0;var p=o||{},b=p.lazy,M=p.number,z=p.trim,r=!b&&"range"!==n,c=b?"change":"range"===n?Xp:"input",i="$event.target.value";z&&(i="$event.target.value.trim()");M&&(i="_n(".concat(i,")"));var a=vp(e,i);r&&(a="if($event.target.composing)return;".concat(a));ap(t,"value","(".concat(e,")")),up(t,c,a,null,!0),(z||M)&&up(t,"blur","$forceUpdate()")}(t,n,p);else{if(!H.isReservedTag(b))return Wp(t,n,p),!1}return!0},text:function(t,e){e.value&&ap(t,"textContent","_s(".concat(e.value,")"),e)},html:function(t,e){e.value&&ap(t,"innerHTML","_s(".concat(e.value,")"),e)}},Mz={expectHTML:!0,modules:oz,directives:bz,isPreTag:function(t){return"pre"===t},isUnaryTag:eM,mustUseProp:sn,canBeLeftOpenTag:oM,isReservedTag:Ln,getTagNamespace:Xn,staticKeys:function(t){return t.reduce((function(t,e){return t.concat(e.staticKeys||[])}),[]).join(",")}(oz)},zz=m((function(t){return f("type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap"+(t?","+t:""))}));function rz(t,e){t&&(nz=zz(e.staticKeys||""),pz=e.isReservedTag||C,cz(t),iz(t,!1))}function cz(t){if(t.static=function(t){if(2===t.type)return!1;if(3===t.type)return!0;return!(!t.pre&&(t.hasBindings||t.if||t.for||q(t.tag)||!pz(t.tag)||function(t){for(;t.parent;){if("template"!==(t=t.parent).tag)return!1;if(t.for)return!0}return!1}(t)||!Object.keys(t).every(nz)))}(t),1===t.type){if(!pz(t.tag)&&"slot"!==t.tag&&null==t.attrsMap["inline-template"])return;for(var e=0,o=t.children.length;e|^function(?:\s+[\w$]+)?\s*\(/,Oz=/\([^)]*?\);*$/,sz=/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*$/,lz={esc:27,tab:9,enter:13,space:32,up:38,left:37,right:39,down:40,delete:[8,46]},dz={esc:["Esc","Escape"],tab:"Tab",enter:"Enter",space:[" ","Spacebar"],up:["Up","ArrowUp"],left:["Left","ArrowLeft"],right:["Right","ArrowRight"],down:["Down","ArrowDown"],delete:["Backspace","Delete","Del"]},uz=function(t){return"if(".concat(t,")return null;")},Az={stop:"$event.stopPropagation();",prevent:"$event.preventDefault();",self:uz("$event.target !== $event.currentTarget"),ctrl:uz("!$event.ctrlKey"),shift:uz("!$event.shiftKey"),alt:uz("!$event.altKey"),meta:uz("!$event.metaKey"),left:uz("'button' in $event && $event.button !== 0"),middle:uz("'button' in $event && $event.button !== 1"),right:uz("'button' in $event && $event.button !== 2")};function fz(t,e){var o=e?"nativeOn:":"on:",n="",p="";for(var b in t){var M=qz(t[b]);t[b]&&t[b].dynamic?p+="".concat(b,",").concat(M,","):n+='"'.concat(b,'":').concat(M,",")}return n="{".concat(n.slice(0,-1),"}"),p?o+"_d(".concat(n,",[").concat(p.slice(0,-1),"])"):o+n}function qz(t){if(!t)return"function(){}";if(Array.isArray(t))return"[".concat(t.map((function(t){return qz(t)})).join(","),"]");var e=sz.test(t.value),o=az.test(t.value),n=sz.test(t.value.replace(Oz,""));if(t.modifiers){var p="",b="",M=[],z=function(e){if(Az[e])b+=Az[e],lz[e]&&M.push(e);else if("exact"===e){var o=t.modifiers;b+=uz(["ctrl","shift","alt","meta"].filter((function(t){return!o[t]})).map((function(t){return"$event.".concat(t,"Key")})).join("||"))}else M.push(e)};for(var r in t.modifiers)z(r);M.length&&(p+=function(t){return"if(!$event.type.indexOf('key')&&"+"".concat(t.map(hz).join("&&"),")return null;")}(M)),b&&(p+=b);var c=e?"return ".concat(t.value,".apply(null, arguments)"):o?"return (".concat(t.value,").apply(null, arguments)"):n?"return ".concat(t.value):t.value;return"function($event){".concat(p).concat(c,"}")}return e||o?t.value:"function($event){".concat(n?"return ".concat(t.value):t.value,"}")}function hz(t){var e=parseInt(t,10);if(e)return"$event.keyCode!==".concat(e);var o=lz[t],n=dz[t];return"_k($event.keyCode,"+"".concat(JSON.stringify(t),",")+"".concat(JSON.stringify(o),",")+"$event.key,"+"".concat(JSON.stringify(n))+")"}var Wz={on:function(t,e){t.wrapListeners=function(t){return"_g(".concat(t,",").concat(e.value,")")}},bind:function(t,e){t.wrapData=function(o){return"_b(".concat(o,",'").concat(t.tag,"',").concat(e.value,",").concat(e.modifiers&&e.modifiers.prop?"true":"false").concat(e.modifiers&&e.modifiers.sync?",true":"",")")}},cloak:T},vz=function(t){this.options=t,this.warn=t.warn||cp,this.transforms=ip(t.modules,"transformCode"),this.dataGenFns=ip(t.modules,"genData"),this.directives=w(w({},Wz),t.directives);var e=t.isReservedTag||C;this.maybeComponent=function(t){return!!t.component||!e(t.tag)},this.onceId=0,this.staticRenderFns=[],this.pre=!1};function gz(t,e){var o=new vz(e),n=t?"script"===t.tag?"null":mz(t,o):'_c("div")';return{render:"with(this){return ".concat(n,"}"),staticRenderFns:o.staticRenderFns}}function mz(t,e){if(t.parent&&(t.pre=t.pre||t.parent.pre),t.staticRoot&&!t.staticProcessed)return Rz(t,e);if(t.once&&!t.onceProcessed)return yz(t,e);if(t.for&&!t.forProcessed)return Xz(t,e);if(t.if&&!t.ifProcessed)return Bz(t,e);if("template"!==t.tag||t.slotTarget||e.pre){if("slot"===t.tag)return function(t,e){var o=t.slotName||'"default"',n=xz(t,e),p="_t(".concat(o).concat(n?",function(){return ".concat(n,"}"):""),b=t.attrs||t.dynamicAttrs?Sz((t.attrs||[]).concat(t.dynamicAttrs||[]).map((function(t){return{name:y(t.name),value:t.value,dynamic:t.dynamic}}))):null,M=t.attrsMap["v-bind"];!b&&!M||n||(p+=",null");b&&(p+=",".concat(b));M&&(p+="".concat(b?"":",null",",").concat(M));return p+")"}(t,e);var o=void 0;if(t.component)o=function(t,e,o){var n=e.inlineTemplate?null:xz(e,o,!0);return"_c(".concat(t,",").concat(_z(e,o)).concat(n?",".concat(n):"",")")}(t.component,t,e);else{var n=void 0,p=e.maybeComponent(t);(!t.plain||t.pre&&p)&&(n=_z(t,e));var b=void 0,M=e.options.bindings;p&&M&&!1!==M.__isScriptSetup&&(b=function(t,e){var o=y(e),n=B(o),p=function(p){return t[e]===p?e:t[o]===p?o:t[n]===p?n:void 0},b=p("setup-const")||p("setup-reactive-const");if(b)return b;var M=p("setup-let")||p("setup-ref")||p("setup-maybe-ref");if(M)return M}(M,t.tag)),b||(b="'".concat(t.tag,"'"));var z=t.inlineTemplate?null:xz(t,e,!0);o="_c(".concat(b).concat(n?",".concat(n):"").concat(z?",".concat(z):"",")")}for(var r=0;r>>0}(M)):"",")")}(t,t.scopedSlots,e),",")),t.model&&(o+="model:{value:".concat(t.model.value,",callback:").concat(t.model.callback,",expression:").concat(t.model.expression,"},")),t.inlineTemplate){var b=function(t,e){var o=t.children[0];0;if(o&&1===o.type){var n=gz(o,e.options);return"inlineTemplate:{render:function(){".concat(n.render,"},staticRenderFns:[").concat(n.staticRenderFns.map((function(t){return"function(){".concat(t,"}")})).join(","),"]}")}}(t,e);b&&(o+="".concat(b,","))}return o=o.replace(/,$/,"")+"}",t.dynamicAttrs&&(o="_b(".concat(o,',"').concat(t.tag,'",').concat(Sz(t.dynamicAttrs),")")),t.wrapData&&(o=t.wrapData(o)),t.wrapListeners&&(o=t.wrapListeners(o)),o}function Nz(t){return 1===t.type&&("slot"===t.tag||t.children.some(Nz))}function wz(t,e){var o=t.attrsMap["slot-scope"];if(t.if&&!t.ifProcessed&&!o)return Bz(t,e,wz,"null");if(t.for&&!t.forProcessed)return Xz(t,e,wz);var n=t.slotScope===HM?"":String(t.slotScope),p="function(".concat(n,"){")+"return ".concat("template"===t.tag?t.if&&o?"(".concat(t.if,")?").concat(xz(t,e)||"undefined",":undefined"):xz(t,e)||"undefined":mz(t,e),"}"),b=n?"":",proxy:true";return"{key:".concat(t.slotTarget||'"default"',",fn:").concat(p).concat(b,"}")}function xz(t,e,o,n,p){var b=t.children;if(b.length){var M=b[0];if(1===b.length&&M.for&&"template"!==M.tag&&"slot"!==M.tag){var z=o?e.maybeComponent(M)?",1":",0":"";return"".concat((n||mz)(M,e)).concat(z)}var r=o?function(t,e){for(var o=0,n=0;n':'
',jz.innerHTML.indexOf(" ")>0}var Uz=!!K&&Hz(!1),Vz=!!K&&Hz(!0),$z=m((function(t){var e=wn(t);return e&&e.innerHTML})),Yz=en.prototype.$mount;en.prototype.$mount=function(t,e){if((t=t&&wn(t))===document.body||t===document.documentElement)return this;var o=this.$options;if(!o.render){var n=o.template;if(n)if("string"==typeof n)"#"===n.charAt(0)&&(n=$z(n));else{if(!n.nodeType)return this;n=n.innerHTML}else t&&(n=function(t){if(t.outerHTML)return t.outerHTML;var e=document.createElement("div");return e.appendChild(t.cloneNode(!0)),e.innerHTML}(t));if(n){0;var p=Fz(n,{outputSourceRange:!1,shouldDecodeNewlines:Uz,shouldDecodeNewlinesForHref:Vz,delimiters:o.delimiters,comments:o.comments},this),b=p.render,M=p.staticRenderFns;o.render=b,o.staticRenderFns=M}}return Yz.call(this,t,e)},en.compile=Fz;var Gz=o(8),Jz=o.n(Gz);const Kz={computed:{Horizon:function(t){function e(){return t.apply(this,arguments)}return e.toString=function(){return t.toString()},e}((function(){return Horizon}))},methods:{formatDate:function(t){return Jz()(1e3*t).add((new Date).getTimezoneOffset()/60)},formatDateIso:function(t){return Jz()(t).add((new Date).getTimezoneOffset()/60)},jobBaseName:function(t){if(!t.includes("\\"))return t;var e=t.split("\\");return e[e.length-1]},autoLoadNewEntries:function(){this.autoLoadsNewEntries?(this.autoLoadsNewEntries=!1,localStorage.autoLoadsNewEntries=0):(this.autoLoadsNewEntries=!0,localStorage.autoLoadsNewEntries=1)},readableTimestamp:function(t){return this.formatDate(t).format("YYYY-MM-DD HH:mm:ss")}}};var Qz=o(9669),Zz=o.n(Qz);const tr=[{path:"/",redirect:"/dashboard"},{path:"/dashboard",name:"dashboard",component:o(7124).Z},{path:"/monitoring",name:"monitoring",component:o(3317).Z},{path:"/monitoring/:tag",component:o(3343).Z,children:[{path:"jobs",name:"monitoring-jobs",component:o(4308).Z,props:{type:"jobs"}},{path:"failed",name:"monitoring-failed",component:o(4308).Z,props:{type:"failed"}}]},{path:"/metrics",redirect:"/metrics/jobs"},{path:"/metrics/",component:o(7042).Z,children:[{path:"jobs",name:"metrics-jobs",component:o(675).Z},{path:"queues",name:"metrics-queues",component:o(8947).Z}]},{path:"/metrics/:type/:slug",name:"metrics-preview",component:o(554).Z},{path:"/jobs/:type",name:"jobs",component:o(2599).Z},{path:"/jobs/pending/:jobId",name:"pending-jobs-preview",component:o(726).Z},{path:"/jobs/completed/:jobId",name:"completed-jobs-preview",component:o(726).Z},{path:"/failed",name:"failed-jobs",component:o(3741).Z},{path:"/failed/:jobId",name:"failed-jobs-preview",component:o(9594).Z},{path:"/batches",name:"batches",component:o(181).Z},{path:"/batches/:batchId",name:"batches-preview",component:o(9272).Z}];function er(t,e){for(var o in e)t[o]=e[o];return t}var or=/[!'()*]/g,nr=function(t){return"%"+t.charCodeAt(0).toString(16)},pr=/%2C/g,br=function(t){return encodeURIComponent(t).replace(or,nr).replace(pr,",")};function Mr(t){try{return decodeURIComponent(t)}catch(t){0}return t}var zr=function(t){return null==t||"object"==typeof t?t:String(t)};function rr(t){var e={};return(t=t.trim().replace(/^(\?|#|&)/,""))?(t.split("&").forEach((function(t){var o=t.replace(/\+/g," ").split("="),n=Mr(o.shift()),p=o.length>0?Mr(o.join("=")):null;void 0===e[n]?e[n]=p:Array.isArray(e[n])?e[n].push(p):e[n]=[e[n],p]})),e):e}function cr(t){var e=t?Object.keys(t).map((function(e){var o=t[e];if(void 0===o)return"";if(null===o)return br(e);if(Array.isArray(o)){var n=[];return o.forEach((function(t){void 0!==t&&(null===t?n.push(br(e)):n.push(br(e)+"="+br(t)))})),n.join("&")}return br(e)+"="+br(o)})).filter((function(t){return t.length>0})).join("&"):null;return e?"?"+e:""}var ir=/\/?$/;function ar(t,e,o,n){var p=n&&n.options.stringifyQuery,b=e.query||{};try{b=Or(b)}catch(t){}var M={name:e.name||t&&t.name,meta:t&&t.meta||{},path:e.path||"/",hash:e.hash||"",query:b,params:e.params||{},fullPath:dr(e,p),matched:t?lr(t):[]};return o&&(M.redirectedFrom=dr(o,p)),Object.freeze(M)}function Or(t){if(Array.isArray(t))return t.map(Or);if(t&&"object"==typeof t){var e={};for(var o in t)e[o]=Or(t[o]);return e}return t}var sr=ar(null,{path:"/"});function lr(t){for(var e=[];t;)e.unshift(t),t=t.parent;return e}function dr(t,e){var o=t.path,n=t.query;void 0===n&&(n={});var p=t.hash;return void 0===p&&(p=""),(o||"/")+(e||cr)(n)+p}function ur(t,e,o){return e===sr?t===e:!!e&&(t.path&&e.path?t.path.replace(ir,"")===e.path.replace(ir,"")&&(o||t.hash===e.hash&&Ar(t.query,e.query)):!(!t.name||!e.name)&&(t.name===e.name&&(o||t.hash===e.hash&&Ar(t.query,e.query)&&Ar(t.params,e.params))))}function Ar(t,e){if(void 0===t&&(t={}),void 0===e&&(e={}),!t||!e)return t===e;var o=Object.keys(t).sort(),n=Object.keys(e).sort();return o.length===n.length&&o.every((function(o,p){var b=t[o];if(n[p]!==o)return!1;var M=e[o];return null==b||null==M?b===M:"object"==typeof b&&"object"==typeof M?Ar(b,M):String(b)===String(M)}))}function fr(t){for(var e=0;e=0&&(e=t.slice(n),t=t.slice(0,n));var p=t.indexOf("?");return p>=0&&(o=t.slice(p+1),t=t.slice(0,p)),{path:t,query:o,hash:e}}(p.path||""),c=e&&e.path||"/",i=r.path?Wr(r.path,c,o||p.append):c,a=function(t,e,o){void 0===e&&(e={});var n,p=o||rr;try{n=p(t||"")}catch(t){n={}}for(var b in e){var M=e[b];n[b]=Array.isArray(M)?M.map(zr):zr(M)}return n}(r.query,p.query,n&&n.options.parseQuery),O=p.hash||r.hash;return O&&"#"!==O.charAt(0)&&(O="#"+O),{_normalized:!0,path:i,query:a,hash:O}}var Fr,Hr=function(){},Ur={name:"RouterLink",props:{to:{type:[String,Object],required:!0},tag:{type:String,default:"a"},custom:Boolean,exact:Boolean,exactPath:Boolean,append:Boolean,replace:Boolean,activeClass:String,exactActiveClass:String,ariaCurrentValue:{type:String,default:"page"},event:{type:[String,Array],default:"click"}},render:function(t){var e=this,o=this.$router,n=this.$route,p=o.resolve(this.to,n,this.append),b=p.location,M=p.route,z=p.href,r={},c=o.options.linkActiveClass,i=o.options.linkExactActiveClass,a=null==c?"router-link-active":c,O=null==i?"router-link-exact-active":i,s=null==this.activeClass?a:this.activeClass,l=null==this.exactActiveClass?O:this.exactActiveClass,d=M.redirectedFrom?ar(null,Ir(M.redirectedFrom),null,o):M;r[l]=ur(n,d,this.exactPath),r[s]=this.exact||this.exactPath?r[l]:function(t,e){return 0===t.path.replace(ir,"/").indexOf(e.path.replace(ir,"/"))&&(!e.hash||t.hash===e.hash)&&function(t,e){for(var o in e)if(!(o in t))return!1;return!0}(t.query,e.query)}(n,d);var u=r[l]?this.ariaCurrentValue:null,A=function(t){Vr(t)&&(e.replace?o.replace(b,Hr):o.push(b,Hr))},f={click:Vr};Array.isArray(this.event)?this.event.forEach((function(t){f[t]=A})):f[this.event]=A;var q={class:r},h=!this.$scopedSlots.$hasNormal&&this.$scopedSlots.default&&this.$scopedSlots.default({href:z,route:M,navigate:A,isActive:r[s],isExactActive:r[l]});if(h){if(1===h.length)return h[0];if(h.length>1||!h.length)return 0===h.length?t():t("span",{},h)}if("a"===this.tag)q.on=f,q.attrs={href:z,"aria-current":u};else{var W=$r(this.$slots.default);if(W){W.isStatic=!1;var v=W.data=er({},W.data);for(var g in v.on=v.on||{},v.on){var m=v.on[g];g in f&&(v.on[g]=Array.isArray(m)?m:[m])}for(var R in f)R in v.on?v.on[R].push(f[R]):v.on[R]=A;var y=W.data.attrs=er({},W.data.attrs);y.href=z,y["aria-current"]=u}else q.on=f}return t(this.tag,q,this.$slots.default)}};function Vr(t){if(!(t.metaKey||t.altKey||t.ctrlKey||t.shiftKey||t.defaultPrevented||void 0!==t.button&&0!==t.button)){if(t.currentTarget&&t.currentTarget.getAttribute){var e=t.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(e))return}return t.preventDefault&&t.preventDefault(),!0}}function $r(t){if(t)for(var e,o=0;o-1&&(z.params[O]=o.params[O]);return z.path=jr(i.path,z.params),r(i,z,M)}if(z.path){z.params={};for(var s=0;s-1}function Rc(t,e){return mc(t)&&t._isRouter&&(null==e||t.type===e)}function yc(t,e,o){var n=function(p){p>=t.length?o():t[p]?e(t[p],(function(){n(p+1)})):n(p+1)};n(0)}function Bc(t){return function(e,o,n){var p=!1,b=0,M=null;Lc(t,(function(t,e,o,z){if("function"==typeof t&&void 0===t.cid){p=!0,b++;var r,c=Nc((function(e){var p;((p=e).__esModule||_c&&"Module"===p[Symbol.toStringTag])&&(e=e.default),t.resolved="function"==typeof e?e:Fr.extend(e),o.components[z]=e,--b<=0&&n()})),i=Nc((function(t){var e="Failed to resolve async component "+z+": "+t;M||(M=mc(t)?t:new Error(e),n(M))}));try{r=t(c,i)}catch(t){i(t)}if(r)if("function"==typeof r.then)r.then(c,i);else{var a=r.component;a&&"function"==typeof a.then&&a.then(c,i)}}})),p||n()}}function Lc(t,e){return Xc(t.map((function(t){return Object.keys(t.components).map((function(o){return e(t.components[o],t.instances[o],t,o)}))})))}function Xc(t){return Array.prototype.concat.apply([],t)}var _c="function"==typeof Symbol&&"symbol"==typeof Symbol.toStringTag;function Nc(t){var e=!1;return function(){for(var o=[],n=arguments.length;n--;)o[n]=arguments[n];if(!e)return e=!0,t.apply(this,o)}}var wc=function(t,e){this.router=t,this.base=function(t){if(!t)if(Yr){var e=document.querySelector("base");t=(t=e&&e.getAttribute("href")||"/").replace(/^https?:\/\/[^\/]+/,"")}else t="/";"/"!==t.charAt(0)&&(t="/"+t);return t.replace(/\/$/,"")}(e),this.current=sr,this.pending=null,this.ready=!1,this.readyCbs=[],this.readyErrorCbs=[],this.errorCbs=[],this.listeners=[]};function xc(t,e,o,n){var p=Lc(t,(function(t,n,p,b){var M=function(t,e){"function"!=typeof t&&(t=Fr.extend(t));return t.options[e]}(t,e);if(M)return Array.isArray(M)?M.map((function(t){return o(t,n,p,b)})):o(M,n,p,b)}));return Xc(n?p.reverse():p)}function Tc(t,e){if(e)return function(){return t.apply(e,arguments)}}wc.prototype.listen=function(t){this.cb=t},wc.prototype.onReady=function(t,e){this.ready?t():(this.readyCbs.push(t),e&&this.readyErrorCbs.push(e))},wc.prototype.onError=function(t){this.errorCbs.push(t)},wc.prototype.transitionTo=function(t,e,o){var n,p=this;try{n=this.router.match(t,this.current)}catch(t){throw this.errorCbs.forEach((function(e){e(t)})),t}var b=this.current;this.confirmTransition(n,(function(){p.updateRoute(n),e&&e(n),p.ensureURL(),p.router.afterHooks.forEach((function(t){t&&t(n,b)})),p.ready||(p.ready=!0,p.readyCbs.forEach((function(t){t(n)})))}),(function(t){o&&o(t),t&&!p.ready&&(Rc(t,qc.redirected)&&b===sr||(p.ready=!0,p.readyErrorCbs.forEach((function(e){e(t)}))))}))},wc.prototype.confirmTransition=function(t,e,o){var n=this,p=this.current;this.pending=t;var b,M,z=function(t){!Rc(t)&&mc(t)&&n.errorCbs.length&&n.errorCbs.forEach((function(e){e(t)})),o&&o(t)},r=t.matched.length-1,c=p.matched.length-1;if(ur(t,p)&&r===c&&t.matched[r]===p.matched[c])return this.ensureURL(),t.hash&&zc(this.router,p,t,!1),z(((M=vc(b=p,t,qc.duplicated,'Avoided redundant navigation to current location: "'+b.fullPath+'".')).name="NavigationDuplicated",M));var i=function(t,e){var o,n=Math.max(t.length,e.length);for(o=0;o0)){var e=this.router,o=e.options.scrollBehavior,n=uc&&o;n&&this.listeners.push(Mc());var p=function(){var o=t.current,p=Sc(t.base);t.current===sr&&p===t._startLocation||t.transitionTo(p,(function(t){n&&zc(e,t,o,!0)}))};window.addEventListener("popstate",p),this.listeners.push((function(){window.removeEventListener("popstate",p)}))}},e.prototype.go=function(t){window.history.go(t)},e.prototype.push=function(t,e,o){var n=this,p=this.current;this.transitionTo(t,(function(t){Ac(vr(n.base+t.fullPath)),zc(n.router,t,p,!1),e&&e(t)}),o)},e.prototype.replace=function(t,e,o){var n=this,p=this.current;this.transitionTo(t,(function(t){fc(vr(n.base+t.fullPath)),zc(n.router,t,p,!1),e&&e(t)}),o)},e.prototype.ensureURL=function(t){if(Sc(this.base)!==this.current.fullPath){var e=vr(this.base+this.current.fullPath);t?Ac(e):fc(e)}},e.prototype.getCurrentLocation=function(){return Sc(this.base)},e}(wc);function Sc(t){var e=window.location.pathname,o=e.toLowerCase(),n=t.toLowerCase();return!t||o!==n&&0!==o.indexOf(vr(n+"/"))||(e=e.slice(t.length)),(e||"/")+window.location.search+window.location.hash}var kc=function(t){function e(e,o,n){t.call(this,e,o),n&&function(t){var e=Sc(t);if(!/^\/#/.test(e))return window.location.replace(vr(t+"/#"+e)),!0}(this.base)||Ec()}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.setupListeners=function(){var t=this;if(!(this.listeners.length>0)){var e=this.router.options.scrollBehavior,o=uc&&e;o&&this.listeners.push(Mc());var n=function(){var e=t.current;Ec()&&t.transitionTo(Dc(),(function(n){o&&zc(t.router,n,e,!0),uc||Ic(n.fullPath)}))},p=uc?"popstate":"hashchange";window.addEventListener(p,n),this.listeners.push((function(){window.removeEventListener(p,n)}))}},e.prototype.push=function(t,e,o){var n=this,p=this.current;this.transitionTo(t,(function(t){jc(t.fullPath),zc(n.router,t,p,!1),e&&e(t)}),o)},e.prototype.replace=function(t,e,o){var n=this,p=this.current;this.transitionTo(t,(function(t){Ic(t.fullPath),zc(n.router,t,p,!1),e&&e(t)}),o)},e.prototype.go=function(t){window.history.go(t)},e.prototype.ensureURL=function(t){var e=this.current.fullPath;Dc()!==e&&(t?jc(e):Ic(e))},e.prototype.getCurrentLocation=function(){return Dc()},e}(wc);function Ec(){var t=Dc();return"/"===t.charAt(0)||(Ic("/"+t),!1)}function Dc(){var t=window.location.href,e=t.indexOf("#");return e<0?"":t=t.slice(e+1)}function Pc(t){var e=window.location.href,o=e.indexOf("#");return(o>=0?e.slice(0,o):e)+"#"+t}function jc(t){uc?Ac(Pc(t)):window.location.hash=t}function Ic(t){uc?fc(Pc(t)):window.location.replace(Pc(t))}var Fc=function(t){function e(e,o){t.call(this,e,o),this.stack=[],this.index=-1}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.push=function(t,e,o){var n=this;this.transitionTo(t,(function(t){n.stack=n.stack.slice(0,n.index+1).concat(t),n.index++,e&&e(t)}),o)},e.prototype.replace=function(t,e,o){var n=this;this.transitionTo(t,(function(t){n.stack=n.stack.slice(0,n.index).concat(t),e&&e(t)}),o)},e.prototype.go=function(t){var e=this,o=this.index+t;if(!(o<0||o>=this.stack.length)){var n=this.stack[o];this.confirmTransition(n,(function(){var t=e.current;e.index=o,e.updateRoute(n),e.router.afterHooks.forEach((function(e){e&&e(n,t)}))}),(function(t){Rc(t,qc.duplicated)&&(e.index=o)}))}},e.prototype.getCurrentLocation=function(){var t=this.stack[this.stack.length-1];return t?t.fullPath:"/"},e.prototype.ensureURL=function(){},e}(wc),Hc=function(t){void 0===t&&(t={}),this.app=null,this.apps=[],this.options=t,this.beforeHooks=[],this.resolveHooks=[],this.afterHooks=[],this.matcher=Qr(t.routes||[],this);var e=t.mode||"hash";switch(this.fallback="history"===e&&!uc&&!1!==t.fallback,this.fallback&&(e="hash"),Yr||(e="abstract"),this.mode=e,e){case"history":this.history=new Cc(this,t.base);break;case"hash":this.history=new kc(this,t.base,this.fallback);break;case"abstract":this.history=new Fc(this,t.base)}},Uc={currentRoute:{configurable:!0}};Hc.prototype.match=function(t,e,o){return this.matcher.match(t,e,o)},Uc.currentRoute.get=function(){return this.history&&this.history.current},Hc.prototype.init=function(t){var e=this;if(this.apps.push(t),t.$once("hook:destroyed",(function(){var o=e.apps.indexOf(t);o>-1&&e.apps.splice(o,1),e.app===t&&(e.app=e.apps[0]||null),e.app||e.history.teardown()})),!this.app){this.app=t;var o=this.history;if(o instanceof Cc||o instanceof kc){var n=function(t){o.setupListeners(),function(t){var n=o.current,p=e.options.scrollBehavior;uc&&p&&"fullPath"in t&&zc(e,t,n,!1)}(t)};o.transitionTo(o.getCurrentLocation(),n,n)}o.listen((function(t){e.apps.forEach((function(e){e._route=t}))}))}},Hc.prototype.beforeEach=function(t){return $c(this.beforeHooks,t)},Hc.prototype.beforeResolve=function(t){return $c(this.resolveHooks,t)},Hc.prototype.afterEach=function(t){return $c(this.afterHooks,t)},Hc.prototype.onReady=function(t,e){this.history.onReady(t,e)},Hc.prototype.onError=function(t){this.history.onError(t)},Hc.prototype.push=function(t,e,o){var n=this;if(!e&&!o&&"undefined"!=typeof Promise)return new Promise((function(e,o){n.history.push(t,e,o)}));this.history.push(t,e,o)},Hc.prototype.replace=function(t,e,o){var n=this;if(!e&&!o&&"undefined"!=typeof Promise)return new Promise((function(e,o){n.history.replace(t,e,o)}));this.history.replace(t,e,o)},Hc.prototype.go=function(t){this.history.go(t)},Hc.prototype.back=function(){this.go(-1)},Hc.prototype.forward=function(){this.go(1)},Hc.prototype.getMatchedComponents=function(t){var e=t?t.matched?t:this.resolve(t).route:this.currentRoute;return e?[].concat.apply([],e.matched.map((function(t){return Object.keys(t.components).map((function(e){return t.components[e]}))}))):[]},Hc.prototype.resolve=function(t,e,o){var n=Ir(t,e=e||this.history.current,o,this),p=this.match(n,e),b=p.redirectedFrom||p.fullPath,M=function(t,e,o){var n="hash"===o?"#"+e:e;return t?vr(t+"/"+n):n}(this.history.base,b,this.mode);return{location:n,route:p,href:M,normalizedTo:n,resolved:p}},Hc.prototype.getRoutes=function(){return this.matcher.getRoutes()},Hc.prototype.addRoute=function(t,e){this.matcher.addRoute(t,e),this.history.current!==sr&&this.history.transitionTo(this.history.getCurrentLocation())},Hc.prototype.addRoutes=function(t){this.matcher.addRoutes(t),this.history.current!==sr&&this.history.transitionTo(this.history.getCurrentLocation())},Object.defineProperties(Hc.prototype,Uc);var Vc=Hc;function $c(t,e){return t.push(e),function(){var o=t.indexOf(e);o>-1&&t.splice(o,1)}}Hc.install=function t(e){if(!t.installed||Fr!==e){t.installed=!0,Fr=e;var o=function(t){return void 0!==t},n=function(t,e){var n=t.$options._parentVnode;o(n)&&o(n=n.data)&&o(n=n.registerRouteInstance)&&n(t,e)};e.mixin({beforeCreate:function(){o(this.$options.router)?(this._routerRoot=this,this._router=this.$options.router,this._router.init(this),e.util.defineReactive(this,"_route",this._router.history.current)):this._routerRoot=this.$parent&&this.$parent._routerRoot||this,n(this,this)},destroyed:function(){n(this)}}),Object.defineProperty(e.prototype,"$router",{get:function(){return this._routerRoot._router}}),Object.defineProperty(e.prototype,"$route",{get:function(){return this._routerRoot._route}}),e.component("RouterView",qr),e.component("RouterLink",Ur);var p=e.config.optionMergeStrategies;p.beforeRouteEnter=p.beforeRouteLeave=p.beforeRouteUpdate=p.created}},Hc.version="3.6.5",Hc.isNavigationFailure=Rc,Hc.NavigationFailureType=qc,Hc.START_LOCATION=sr,Yr&&window.Vue&&window.Vue.use(Hc);var Yc=o(4566),Gc=o.n(Yc);window.Popper=o(8981).default;try{window.$=window.jQuery=o(9755),o(3734)}catch(t){}var Jc=document.head.querySelector('meta[name="csrf-token"]');Zz().defaults.headers.common["X-Requested-With"]="XMLHttpRequest",Jc&&(Zz().defaults.headers.common["X-CSRF-TOKEN"]=Jc.content),en.use(Vc),en.prototype.$http=Zz().create(),window.Horizon.basePath="/"+window.Horizon.path;var Kc=window.Horizon.basePath+"/";""!==window.Horizon.path&&"/"!==window.Horizon.path||(Kc="/",window.Horizon.basePath="");var Qc=new Vc({routes:tr,mode:"history",base:Kc});en.component("vue-json-pretty",Gc()),en.component("alert",o(2660).Z),en.mixin(Kz),en.directive("tooltip",(function(t,e){$(t).tooltip({title:e.value,placement:e.arg,trigger:"hover"})})),new en({el:"#horizon",router:Qc,data:function(){return{alert:{type:null,autoClose:0,message:"",confirmationProceed:null,confirmationCancel:null},autoLoadsNewEntries:"1"===localStorage.autoLoadsNewEntries}}})},3734:function(t,e,o){!function(t,e,o){"use strict";function n(t){return t&&"object"==typeof t&&"default"in t?t:{default:t}}var p=n(e),b=n(o);function M(t,e){for(var o=0;o=M)throw new Error("Bootstrap's JavaScript requires at least jQuery v1.9.1 but less than v4.0.0")}};f.jQueryDetection(),A();var q="alert",h="4.6.2",W="bs.alert",v="."+W,g=".data-api",m=p.default.fn[q],R="alert",y="fade",B="show",L="close"+v,X="closed"+v,_="click"+v+g,N='[data-dismiss="alert"]',w=function(){function t(t){this._element=t}var e=t.prototype;return e.close=function(t){var e=this._element;t&&(e=this._getRootElement(t)),this._triggerCloseEvent(e).isDefaultPrevented()||this._removeElement(e)},e.dispose=function(){p.default.removeData(this._element,W),this._element=null},e._getRootElement=function(t){var e=f.getSelectorFromElement(t),o=!1;return e&&(o=document.querySelector(e)),o||(o=p.default(t).closest("."+R)[0]),o},e._triggerCloseEvent=function(t){var e=p.default.Event(L);return p.default(t).trigger(e),e},e._removeElement=function(t){var e=this;if(p.default(t).removeClass(B),p.default(t).hasClass(y)){var o=f.getTransitionDurationFromElement(t);p.default(t).one(f.TRANSITION_END,(function(o){return e._destroyElement(t,o)})).emulateTransitionEnd(o)}else this._destroyElement(t)},e._destroyElement=function(t){p.default(t).detach().trigger(X).remove()},t._jQueryInterface=function(e){return this.each((function(){var o=p.default(this),n=o.data(W);n||(n=new t(this),o.data(W,n)),"close"===e&&n[e](this)}))},t._handleDismiss=function(t){return function(e){e&&e.preventDefault(),t.close(this)}},z(t,null,[{key:"VERSION",get:function(){return h}}]),t}();p.default(document).on(_,N,w._handleDismiss(new w)),p.default.fn[q]=w._jQueryInterface,p.default.fn[q].Constructor=w,p.default.fn[q].noConflict=function(){return p.default.fn[q]=m,w._jQueryInterface};var x="button",T="4.6.2",C="bs.button",S="."+C,k=".data-api",E=p.default.fn[x],D="active",P="btn",j="focus",I="click"+S+k,F="focus"+S+k+" blur"+S+k,H="load"+S+k,U='[data-toggle^="button"]',V='[data-toggle="buttons"]',$='[data-toggle="button"]',Y='[data-toggle="buttons"] .btn',G='input:not([type="hidden"])',J=".active",K=".btn",Q=function(){function t(t){this._element=t,this.shouldAvoidTriggerChange=!1}var e=t.prototype;return e.toggle=function(){var t=!0,e=!0,o=p.default(this._element).closest(V)[0];if(o){var n=this._element.querySelector(G);if(n){if("radio"===n.type)if(n.checked&&this._element.classList.contains(D))t=!1;else{var b=o.querySelector(J);b&&p.default(b).removeClass(D)}t&&("checkbox"!==n.type&&"radio"!==n.type||(n.checked=!this._element.classList.contains(D)),this.shouldAvoidTriggerChange||p.default(n).trigger("change")),n.focus(),e=!1}}this._element.hasAttribute("disabled")||this._element.classList.contains("disabled")||(e&&this._element.setAttribute("aria-pressed",!this._element.classList.contains(D)),t&&p.default(this._element).toggleClass(D))},e.dispose=function(){p.default.removeData(this._element,C),this._element=null},t._jQueryInterface=function(e,o){return this.each((function(){var n=p.default(this),b=n.data(C);b||(b=new t(this),n.data(C,b)),b.shouldAvoidTriggerChange=o,"toggle"===e&&b[e]()}))},z(t,null,[{key:"VERSION",get:function(){return T}}]),t}();p.default(document).on(I,U,(function(t){var e=t.target,o=e;if(p.default(e).hasClass(P)||(e=p.default(e).closest(K)[0]),!e||e.hasAttribute("disabled")||e.classList.contains("disabled"))t.preventDefault();else{var n=e.querySelector(G);if(n&&(n.hasAttribute("disabled")||n.classList.contains("disabled")))return void t.preventDefault();"INPUT"!==o.tagName&&"LABEL"===e.tagName||Q._jQueryInterface.call(p.default(e),"toggle","INPUT"===o.tagName)}})).on(F,U,(function(t){var e=p.default(t.target).closest(K)[0];p.default(e).toggleClass(j,/^focus(in)?$/.test(t.type))})),p.default(window).on(H,(function(){for(var t=[].slice.call(document.querySelectorAll(Y)),e=0,o=t.length;e0,this._pointerEvent=Boolean(window.PointerEvent||window.MSPointerEvent),this._addEventListeners()}var e=t.prototype;return e.next=function(){this._isSliding||this._slide(At)},e.nextWhenVisible=function(){var t=p.default(this._element);!document.hidden&&t.is(":visible")&&"hidden"!==t.css("visibility")&&this.next()},e.prev=function(){this._isSliding||this._slide(ft)},e.pause=function(t){t||(this._isPaused=!0),this._element.querySelector(Et)&&(f.triggerTransitionEnd(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null},e.cycle=function(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))},e.to=function(t){var e=this;this._activeElement=this._element.querySelector(Ct);var o=this._getItemIndex(this._activeElement);if(!(t>this._items.length-1||t<0))if(this._isSliding)p.default(this._element).one(vt,(function(){return e.to(t)}));else{if(o===t)return this.pause(),void this.cycle();var n=t>o?At:ft;this._slide(n,this._items[t])}},e.dispose=function(){p.default(this._element).off(ot),p.default.removeData(this._element,et),this._items=null,this._config=null,this._element=null,this._interval=null,this._isPaused=null,this._isSliding=null,this._activeElement=null,this._indicatorsElement=null},e._getConfig=function(t){return t=r({},It,t),f.typeCheckConfig(Z,t,Ft),t},e._handleSwipe=function(){var t=Math.abs(this.touchDeltaX);if(!(t<=rt)){var e=t/this.touchDeltaX;this.touchDeltaX=0,e>0&&this.prev(),e<0&&this.next()}},e._addEventListeners=function(){var t=this;this._config.keyboard&&p.default(this._element).on(gt,(function(e){return t._keydown(e)})),"hover"===this._config.pause&&p.default(this._element).on(mt,(function(e){return t.pause(e)})).on(Rt,(function(e){return t.cycle(e)})),this._config.touch&&this._addTouchEventListeners()},e._addTouchEventListeners=function(){var t=this;if(this._touchSupported){var e=function(e){t._pointerEvent&&Ht[e.originalEvent.pointerType.toUpperCase()]?t.touchStartX=e.originalEvent.clientX:t._pointerEvent||(t.touchStartX=e.originalEvent.touches[0].clientX)},o=function(e){t.touchDeltaX=e.originalEvent.touches&&e.originalEvent.touches.length>1?0:e.originalEvent.touches[0].clientX-t.touchStartX},n=function(e){t._pointerEvent&&Ht[e.originalEvent.pointerType.toUpperCase()]&&(t.touchDeltaX=e.originalEvent.clientX-t.touchStartX),t._handleSwipe(),"hover"===t._config.pause&&(t.pause(),t.touchTimeout&&clearTimeout(t.touchTimeout),t.touchTimeout=setTimeout((function(e){return t.cycle(e)}),zt+t._config.interval))};p.default(this._element.querySelectorAll(kt)).on(Nt,(function(t){return t.preventDefault()})),this._pointerEvent?(p.default(this._element).on(Xt,(function(t){return e(t)})),p.default(this._element).on(_t,(function(t){return n(t)})),this._element.classList.add(ut)):(p.default(this._element).on(yt,(function(t){return e(t)})),p.default(this._element).on(Bt,(function(t){return o(t)})),p.default(this._element).on(Lt,(function(t){return n(t)})))}},e._keydown=function(t){if(!/input|textarea/i.test(t.target.tagName))switch(t.which){case bt:t.preventDefault(),this.prev();break;case Mt:t.preventDefault(),this.next()}},e._getItemIndex=function(t){return this._items=t&&t.parentNode?[].slice.call(t.parentNode.querySelectorAll(St)):[],this._items.indexOf(t)},e._getItemByDirection=function(t,e){var o=t===At,n=t===ft,p=this._getItemIndex(e),b=this._items.length-1;if((n&&0===p||o&&p===b)&&!this._config.wrap)return e;var M=(p+(t===ft?-1:1))%this._items.length;return-1===M?this._items[this._items.length-1]:this._items[M]},e._triggerSlideEvent=function(t,e){var o=this._getItemIndex(t),n=this._getItemIndex(this._element.querySelector(Ct)),b=p.default.Event(Wt,{relatedTarget:t,direction:e,from:n,to:o});return p.default(this._element).trigger(b),b},e._setActiveIndicatorElement=function(t){if(this._indicatorsElement){var e=[].slice.call(this._indicatorsElement.querySelectorAll(Tt));p.default(e).removeClass(it);var o=this._indicatorsElement.children[this._getItemIndex(t)];o&&p.default(o).addClass(it)}},e._updateInterval=function(){var t=this._activeElement||this._element.querySelector(Ct);if(t){var e=parseInt(t.getAttribute("data-interval"),10);e?(this._config.defaultInterval=this._config.defaultInterval||this._config.interval,this._config.interval=e):this._config.interval=this._config.defaultInterval||this._config.interval}},e._slide=function(t,e){var o,n,b,M=this,z=this._element.querySelector(Ct),r=this._getItemIndex(z),c=e||z&&this._getItemByDirection(t,z),i=this._getItemIndex(c),a=Boolean(this._interval);if(t===At?(o=st,n=lt,b=qt):(o=Ot,n=dt,b=ht),c&&p.default(c).hasClass(it))this._isSliding=!1;else if(!this._triggerSlideEvent(c,b).isDefaultPrevented()&&z&&c){this._isSliding=!0,a&&this.pause(),this._setActiveIndicatorElement(c),this._activeElement=c;var O=p.default.Event(vt,{relatedTarget:c,direction:b,from:r,to:i});if(p.default(this._element).hasClass(at)){p.default(c).addClass(n),f.reflow(c),p.default(z).addClass(o),p.default(c).addClass(o);var s=f.getTransitionDurationFromElement(z);p.default(z).one(f.TRANSITION_END,(function(){p.default(c).removeClass(o+" "+n).addClass(it),p.default(z).removeClass(it+" "+n+" "+o),M._isSliding=!1,setTimeout((function(){return p.default(M._element).trigger(O)}),0)})).emulateTransitionEnd(s)}else p.default(z).removeClass(it),p.default(c).addClass(it),this._isSliding=!1,p.default(this._element).trigger(O);a&&this.cycle()}},t._jQueryInterface=function(e){return this.each((function(){var o=p.default(this).data(et),n=r({},It,p.default(this).data());"object"==typeof e&&(n=r({},n,e));var b="string"==typeof e?e:n.slide;if(o||(o=new t(this,n),p.default(this).data(et,o)),"number"==typeof e)o.to(e);else if("string"==typeof b){if(void 0===o[b])throw new TypeError('No method named "'+b+'"');o[b]()}else n.interval&&n.ride&&(o.pause(),o.cycle())}))},t._dataApiClickHandler=function(e){var o=f.getSelectorFromElement(this);if(o){var n=p.default(o)[0];if(n&&p.default(n).hasClass(ct)){var b=r({},p.default(n).data(),p.default(this).data()),M=this.getAttribute("data-slide-to");M&&(b.interval=!1),t._jQueryInterface.call(p.default(n),b),M&&p.default(n).data(et).to(M),e.preventDefault()}}},z(t,null,[{key:"VERSION",get:function(){return tt}},{key:"Default",get:function(){return It}}]),t}();p.default(document).on(xt,Pt,Ut._dataApiClickHandler),p.default(window).on(wt,(function(){for(var t=[].slice.call(document.querySelectorAll(jt)),e=0,o=t.length;e0&&(this._selector=M,this._triggerArray.push(b))}this._parent=this._config.parent?this._getParent():null,this._config.parent||this._addAriaAndCollapsedClass(this._element,this._triggerArray),this._config.toggle&&this.toggle()}var e=t.prototype;return e.toggle=function(){p.default(this._element).hasClass(Qt)?this.hide():this.show()},e.show=function(){var e,o,n=this;if(!(this._isTransitioning||p.default(this._element).hasClass(Qt)||(this._parent&&0===(e=[].slice.call(this._parent.querySelectorAll(ce)).filter((function(t){return"string"==typeof n._config.parent?t.getAttribute("data-parent")===n._config.parent:t.classList.contains(Zt)}))).length&&(e=null),e&&(o=p.default(e).not(this._selector).data(Yt))&&o._isTransitioning))){var b=p.default.Event(pe);if(p.default(this._element).trigger(b),!b.isDefaultPrevented()){e&&(t._jQueryInterface.call(p.default(e).not(this._selector),"hide"),o||p.default(e).data(Yt,null));var M=this._getDimension();p.default(this._element).removeClass(Zt).addClass(te),this._element.style[M]=0,this._triggerArray.length&&p.default(this._triggerArray).removeClass(ee).attr("aria-expanded",!0),this.setTransitioning(!0);var z=function(){p.default(n._element).removeClass(te).addClass(Zt+" "+Qt),n._element.style[M]="",n.setTransitioning(!1),p.default(n._element).trigger(be)},r="scroll"+(M[0].toUpperCase()+M.slice(1)),c=f.getTransitionDurationFromElement(this._element);p.default(this._element).one(f.TRANSITION_END,z).emulateTransitionEnd(c),this._element.style[M]=this._element[r]+"px"}}},e.hide=function(){var t=this;if(!this._isTransitioning&&p.default(this._element).hasClass(Qt)){var e=p.default.Event(Me);if(p.default(this._element).trigger(e),!e.isDefaultPrevented()){var o=this._getDimension();this._element.style[o]=this._element.getBoundingClientRect()[o]+"px",f.reflow(this._element),p.default(this._element).addClass(te).removeClass(Zt+" "+Qt);var n=this._triggerArray.length;if(n>0)for(var b=0;b0},e._getOffset=function(){var t=this,e={};return"function"==typeof this._config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t._config.offset(e.offsets,t._element)),e}:e.offset=this._config.offset,e},e._getPopperConfig=function(){var t={placement:this._getPlacement(),modifiers:{offset:this._getOffset(),flip:{enabled:this._config.flip},preventOverflow:{boundariesElement:this._config.boundary}}};return"static"===this._config.display&&(t.modifiers.applyStyle={enabled:!1}),r({},t,this._config.popperConfig)},t._jQueryInterface=function(e){return this.each((function(){var o=p.default(this).data(ue);if(o||(o=new t(this,"object"==typeof e?e:null),p.default(this).data(ue,o)),"string"==typeof e){if(void 0===o[e])throw new TypeError('No method named "'+e+'"');o[e]()}}))},t._clearMenus=function(e){if(!e||e.which!==Re&&("keyup"!==e.type||e.which===ve))for(var o=[].slice.call(document.querySelectorAll(Ie)),n=0,b=o.length;n0&&M--,e.which===me&&Mdocument.documentElement.clientHeight;o||(this._element.style.overflowY="hidden"),this._element.classList.add(uo);var n=f.getTransitionDurationFromElement(this._dialog);p.default(this._element).off(f.TRANSITION_END),p.default(this._element).one(f.TRANSITION_END,(function(){t._element.classList.remove(uo),o||p.default(t._element).one(f.TRANSITION_END,(function(){t._element.style.overflowY=""})).emulateTransitionEnd(t._element,n)})).emulateTransitionEnd(n),this._element.focus()}},e._showElement=function(t){var e=this,o=p.default(this._element).hasClass(so),n=this._dialog?this._dialog.querySelector(_o):null;this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.appendChild(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),p.default(this._dialog).hasClass(co)&&n?n.scrollTop=0:this._element.scrollTop=0,o&&f.reflow(this._element),p.default(this._element).addClass(lo),this._config.focus&&this._enforceFocus();var b=p.default.Event(Wo,{relatedTarget:t}),M=function(){e._config.focus&&e._element.focus(),e._isTransitioning=!1,p.default(e._element).trigger(b)};if(o){var z=f.getTransitionDurationFromElement(this._dialog);p.default(this._dialog).one(f.TRANSITION_END,M).emulateTransitionEnd(z)}else M()},e._enforceFocus=function(){var t=this;p.default(document).off(vo).on(vo,(function(e){document!==e.target&&t._element!==e.target&&0===p.default(t._element).has(e.target).length&&t._element.focus()}))},e._setEscapeEvent=function(){var t=this;this._isShown?p.default(this._element).on(Ro,(function(e){t._config.keyboard&&e.which===ro?(e.preventDefault(),t.hide()):t._config.keyboard||e.which!==ro||t._triggerBackdropTransition()})):this._isShown||p.default(this._element).off(Ro)},e._setResizeEvent=function(){var t=this;this._isShown?p.default(window).on(go,(function(e){return t.handleUpdate(e)})):p.default(window).off(go)},e._hideModal=function(){var t=this;this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._showBackdrop((function(){p.default(document.body).removeClass(Oo),t._resetAdjustments(),t._resetScrollbar(),p.default(t._element).trigger(qo)}))},e._removeBackdrop=function(){this._backdrop&&(p.default(this._backdrop).remove(),this._backdrop=null)},e._showBackdrop=function(t){var e=this,o=p.default(this._element).hasClass(so)?so:"";if(this._isShown&&this._config.backdrop){if(this._backdrop=document.createElement("div"),this._backdrop.className=ao,o&&this._backdrop.classList.add(o),p.default(this._backdrop).appendTo(document.body),p.default(this._element).on(mo,(function(t){e._ignoreBackdropClick?e._ignoreBackdropClick=!1:t.target===t.currentTarget&&("static"===e._config.backdrop?e._triggerBackdropTransition():e.hide())})),o&&f.reflow(this._backdrop),p.default(this._backdrop).addClass(lo),!t)return;if(!o)return void t();var n=f.getTransitionDurationFromElement(this._backdrop);p.default(this._backdrop).one(f.TRANSITION_END,t).emulateTransitionEnd(n)}else if(!this._isShown&&this._backdrop){p.default(this._backdrop).removeClass(lo);var b=function(){e._removeBackdrop(),t&&t()};if(p.default(this._element).hasClass(so)){var M=f.getTransitionDurationFromElement(this._backdrop);p.default(this._backdrop).one(f.TRANSITION_END,b).emulateTransitionEnd(M)}else b()}else t&&t()},e._adjustDialog=function(){var t=this._element.scrollHeight>document.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},e._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},e._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=Math.round(t.left+t.right)
',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",customClass:"",sanitize:!0,sanitizeFn:null,whiteList:Do,popperConfig:null},an={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(number|string|function)",container:"(string|element|boolean)",fallbackPlacement:"(string|array)",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",whiteList:"object",popperConfig:"(null|object)"},On={HIDE:"hide"+$o,HIDDEN:"hidden"+$o,SHOW:"show"+$o,SHOWN:"shown"+$o,INSERTED:"inserted"+$o,CLICK:"click"+$o,FOCUSIN:"focusin"+$o,FOCUSOUT:"focusout"+$o,MOUSEENTER:"mouseenter"+$o,MOUSELEAVE:"mouseleave"+$o},sn=function(){function t(t,e){if(void 0===b.default)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var e=t.prototype;return e.enable=function(){this._isEnabled=!0},e.disable=function(){this._isEnabled=!1},e.toggleEnabled=function(){this._isEnabled=!this._isEnabled},e.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,o=p.default(t.currentTarget).data(e);o||(o=new this.constructor(t.currentTarget,this._getDelegateConfig()),p.default(t.currentTarget).data(e,o)),o._activeTrigger.click=!o._activeTrigger.click,o._isWithActiveTrigger()?o._enter(null,o):o._leave(null,o)}else{if(p.default(this.getTipElement()).hasClass(Zo))return void this._leave(null,this);this._enter(null,this)}},e.dispose=function(){clearTimeout(this._timeout),p.default.removeData(this.element,this.constructor.DATA_KEY),p.default(this.element).off(this.constructor.EVENT_KEY),p.default(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&p.default(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},e.show=function(){var t=this;if("none"===p.default(this.element).css("display"))throw new Error("Please use show on visible elements");var e=p.default.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){p.default(this.element).trigger(e);var o=f.findShadowRoot(this.element),n=p.default.contains(null!==o?o:this.element.ownerDocument.documentElement,this.element);if(e.isDefaultPrevented()||!n)return;var M=this.getTipElement(),z=f.getUID(this.constructor.NAME);M.setAttribute("id",z),this.element.setAttribute("aria-describedby",z),this.setContent(),this.config.animation&&p.default(M).addClass(Qo);var r="function"==typeof this.config.placement?this.config.placement.call(this,M,this.element):this.config.placement,c=this._getAttachment(r);this.addAttachmentClass(c);var i=this._getContainer();p.default(M).data(this.constructor.DATA_KEY,this),p.default.contains(this.element.ownerDocument.documentElement,this.tip)||p.default(M).appendTo(i),p.default(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new b.default(this.element,M,this._getPopperConfig(c)),p.default(M).addClass(Zo),p.default(M).addClass(this.config.customClass),"ontouchstart"in document.documentElement&&p.default(document.body).children().on("mouseover",null,p.default.noop);var a=function(){t.config.animation&&t._fixTransition();var e=t._hoverState;t._hoverState=null,p.default(t.element).trigger(t.constructor.Event.SHOWN),e===en&&t._leave(null,t)};if(p.default(this.tip).hasClass(Qo)){var O=f.getTransitionDurationFromElement(this.tip);p.default(this.tip).one(f.TRANSITION_END,a).emulateTransitionEnd(O)}else a()}},e.hide=function(t){var e=this,o=this.getTipElement(),n=p.default.Event(this.constructor.Event.HIDE),b=function(){e._hoverState!==tn&&o.parentNode&&o.parentNode.removeChild(o),e._cleanTipClass(),e.element.removeAttribute("aria-describedby"),p.default(e.element).trigger(e.constructor.Event.HIDDEN),null!==e._popper&&e._popper.destroy(),t&&t()};if(p.default(this.element).trigger(n),!n.isDefaultPrevented()){if(p.default(o).removeClass(Zo),"ontouchstart"in document.documentElement&&p.default(document.body).children().off("mouseover",null,p.default.noop),this._activeTrigger[Mn]=!1,this._activeTrigger[bn]=!1,this._activeTrigger[pn]=!1,p.default(this.tip).hasClass(Qo)){var M=f.getTransitionDurationFromElement(o);p.default(o).one(f.TRANSITION_END,b).emulateTransitionEnd(M)}else b();this._hoverState=""}},e.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},e.isWithContent=function(){return Boolean(this.getTitle())},e.addAttachmentClass=function(t){p.default(this.getTipElement()).addClass(Go+"-"+t)},e.getTipElement=function(){return this.tip=this.tip||p.default(this.config.template)[0],this.tip},e.setContent=function(){var t=this.getTipElement();this.setElementContent(p.default(t.querySelectorAll(on)),this.getTitle()),p.default(t).removeClass(Qo+" "+Zo)},e.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=Fo(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?p.default(e).parent().is(t)||t.empty().append(e):t.text(p.default(e).text())},e.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t||(t="function"==typeof this.config.title?this.config.title.call(this.element):this.config.title),t},e._getPopperConfig=function(t){var e=this;return r({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:nn},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},this.config.popperConfig)},e._getOffset=function(){var t=this,e={};return"function"==typeof this.config.offset?e.fn=function(e){return e.offsets=r({},e.offsets,t.config.offset(e.offsets,t.element)),e}:e.offset=this.config.offset,e},e._getContainer=function(){return!1===this.config.container?document.body:f.isElement(this.config.container)?p.default(this.config.container):p.default(document).find(this.config.container)},e._getAttachment=function(t){return rn[t.toUpperCase()]},e._setListeners=function(){var t=this;this.config.trigger.split(" ").forEach((function(e){if("click"===e)p.default(t.element).on(t.constructor.Event.CLICK,t.config.selector,(function(e){return t.toggle(e)}));else if(e!==zn){var o=e===pn?t.constructor.Event.MOUSEENTER:t.constructor.Event.FOCUSIN,n=e===pn?t.constructor.Event.MOUSELEAVE:t.constructor.Event.FOCUSOUT;p.default(t.element).on(o,t.config.selector,(function(e){return t._enter(e)})).on(n,t.config.selector,(function(e){return t._leave(e)}))}})),this._hideModalHandler=function(){t.element&&t.hide()},p.default(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=r({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},e._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");(this.element.getAttribute("title")||"string"!==t)&&(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},e._enter=function(t,e){var o=this.constructor.DATA_KEY;(e=e||p.default(t.currentTarget).data(o))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),p.default(t.currentTarget).data(o,e)),t&&(e._activeTrigger["focusin"===t.type?bn:pn]=!0),p.default(e.getTipElement()).hasClass(Zo)||e._hoverState===tn?e._hoverState=tn:(clearTimeout(e._timeout),e._hoverState=tn,e.config.delay&&e.config.delay.show?e._timeout=setTimeout((function(){e._hoverState===tn&&e.show()}),e.config.delay.show):e.show())},e._leave=function(t,e){var o=this.constructor.DATA_KEY;(e=e||p.default(t.currentTarget).data(o))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),p.default(t.currentTarget).data(o,e)),t&&(e._activeTrigger["focusout"===t.type?bn:pn]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=en,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout((function(){e._hoverState===en&&e.hide()}),e.config.delay.hide):e.hide())},e._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},e._getConfig=function(t){var e=p.default(this.element).data();return Object.keys(e).forEach((function(t){-1!==Ko.indexOf(t)&&delete e[t]})),"number"==typeof(t=r({},this.constructor.Default,e,"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),f.typeCheckConfig(Ho,t,this.constructor.DefaultType),t.sanitize&&(t.template=Fo(t.template,t.whiteList,t.sanitizeFn)),t},e._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},e._cleanTipClass=function(){var t=p.default(this.getTipElement()),e=t.attr("class").match(Jo);null!==e&&e.length&&t.removeClass(e.join(""))},e._handlePopperPlacementChange=function(t){this.tip=t.instance.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},e._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(p.default(t).removeClass(Qo),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},t._jQueryInterface=function(e){return this.each((function(){var o=p.default(this),n=o.data(Vo),b="object"==typeof e&&e;if((n||!/dispose|hide/.test(e))&&(n||(n=new t(this,b),o.data(Vo,n)),"string"==typeof e)){if(void 0===n[e])throw new TypeError('No method named "'+e+'"');n[e]()}}))},z(t,null,[{key:"VERSION",get:function(){return Uo}},{key:"Default",get:function(){return cn}},{key:"NAME",get:function(){return Ho}},{key:"DATA_KEY",get:function(){return Vo}},{key:"Event",get:function(){return On}},{key:"EVENT_KEY",get:function(){return $o}},{key:"DefaultType",get:function(){return an}}]),t}();p.default.fn[Ho]=sn._jQueryInterface,p.default.fn[Ho].Constructor=sn,p.default.fn[Ho].noConflict=function(){return p.default.fn[Ho]=Yo,sn._jQueryInterface};var ln="popover",dn="4.6.2",un="bs.popover",An="."+un,fn=p.default.fn[ln],qn="bs-popover",hn=new RegExp("(^|\\s)"+qn+"\\S+","g"),Wn="fade",vn="show",gn=".popover-header",mn=".popover-body",Rn=r({},sn.Default,{placement:"right",trigger:"click",content:"",template:''}),yn=r({},sn.DefaultType,{content:"(string|element|function)"}),Bn={HIDE:"hide"+An,HIDDEN:"hidden"+An,SHOW:"show"+An,SHOWN:"shown"+An,INSERTED:"inserted"+An,CLICK:"click"+An,FOCUSIN:"focusin"+An,FOCUSOUT:"focusout"+An,MOUSEENTER:"mouseenter"+An,MOUSELEAVE:"mouseleave"+An},Ln=function(t){function e(){return t.apply(this,arguments)||this}c(e,t);var o=e.prototype;return o.isWithContent=function(){return this.getTitle()||this._getContent()},o.addAttachmentClass=function(t){p.default(this.getTipElement()).addClass(qn+"-"+t)},o.getTipElement=function(){return this.tip=this.tip||p.default(this.config.template)[0],this.tip},o.setContent=function(){var t=p.default(this.getTipElement());this.setElementContent(t.find(gn),this.getTitle());var e=this._getContent();"function"==typeof e&&(e=e.call(this.element)),this.setElementContent(t.find(mn),e),t.removeClass(Wn+" "+vn)},o._getContent=function(){return this.element.getAttribute("data-content")||this.config.content},o._cleanTipClass=function(){var t=p.default(this.getTipElement()),e=t.attr("class").match(hn);null!==e&&e.length>0&&t.removeClass(e.join(""))},e._jQueryInterface=function(t){return this.each((function(){var o=p.default(this).data(un),n="object"==typeof t?t:null;if((o||!/dispose|hide/.test(t))&&(o||(o=new e(this,n),p.default(this).data(un,o)),"string"==typeof t)){if(void 0===o[t])throw new TypeError('No method named "'+t+'"');o[t]()}}))},z(e,null,[{key:"VERSION",get:function(){return dn}},{key:"Default",get:function(){return Rn}},{key:"NAME",get:function(){return ln}},{key:"DATA_KEY",get:function(){return un}},{key:"Event",get:function(){return Bn}},{key:"EVENT_KEY",get:function(){return An}},{key:"DefaultType",get:function(){return yn}}]),e}(sn);p.default.fn[ln]=Ln._jQueryInterface,p.default.fn[ln].Constructor=Ln,p.default.fn[ln].noConflict=function(){return p.default.fn[ln]=fn,Ln._jQueryInterface};var Xn="scrollspy",_n="4.6.2",Nn="bs.scrollspy",wn="."+Nn,xn=".data-api",Tn=p.default.fn[Xn],Cn="dropdown-item",Sn="active",kn="activate"+wn,En="scroll"+wn,Dn="load"+wn+xn,Pn="offset",jn="position",In='[data-spy="scroll"]',Fn=".nav, .list-group",Hn=".nav-link",Un=".nav-item",Vn=".list-group-item",$n=".dropdown",Yn=".dropdown-item",Gn=".dropdown-toggle",Jn={offset:10,method:"auto",target:""},Kn={offset:"number",method:"string",target:"(string|element)"},Qn=function(){function t(t,e){var o=this;this._element=t,this._scrollElement="BODY"===t.tagName?window:t,this._config=this._getConfig(e),this._selector=this._config.target+" "+Hn+","+this._config.target+" "+Vn+","+this._config.target+" "+Yn,this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,p.default(this._scrollElement).on(En,(function(t){return o._process(t)})),this.refresh(),this._process()}var e=t.prototype;return e.refresh=function(){var t=this,e=this._scrollElement===this._scrollElement.window?Pn:jn,o="auto"===this._config.method?e:this._config.method,n=o===jn?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),[].slice.call(document.querySelectorAll(this._selector)).map((function(t){var e,b=f.getSelectorFromElement(t);if(b&&(e=document.querySelector(b)),e){var M=e.getBoundingClientRect();if(M.width||M.height)return[p.default(e)[o]().top+n,b]}return null})).filter(Boolean).sort((function(t,e){return t[0]-e[0]})).forEach((function(e){t._offsets.push(e[0]),t._targets.push(e[1])}))},e.dispose=function(){p.default.removeData(this._element,Nn),p.default(this._scrollElement).off(wn),this._element=null,this._scrollElement=null,this._config=null,this._selector=null,this._offsets=null,this._targets=null,this._activeTarget=null,this._scrollHeight=null},e._getConfig=function(t){if("string"!=typeof(t=r({},Jn,"object"==typeof t&&t?t:{})).target&&f.isElement(t.target)){var e=p.default(t.target).attr("id");e||(e=f.getUID(Xn),p.default(t.target).attr("id",e)),t.target="#"+e}return f.typeCheckConfig(Xn,t,Kn),t},e._getScrollTop=function(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop},e._getScrollHeight=function(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)},e._getOffsetHeight=function(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height},e._process=function(){var t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),o=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=o){var n=this._targets[this._targets.length-1];this._activeTarget!==n&&this._activate(n)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(var p=this._offsets.length;p--;)this._activeTarget!==this._targets[p]&&t>=this._offsets[p]&&(void 0===this._offsets[p+1]||t1&&(p-=1)),[360*p,100*b,100*c]},p.rgb.hwb=function(t){var e=t[0],o=t[1],n=t[2];return[p.rgb.hsl(t)[0],1/255*Math.min(e,Math.min(o,n))*100,100*(n=1-1/255*Math.max(e,Math.max(o,n)))]},p.rgb.cmyk=function(t){var e,o=t[0]/255,n=t[1]/255,p=t[2]/255;return[100*((1-o-(e=Math.min(1-o,1-n,1-p)))/(1-e)||0),100*((1-n-e)/(1-e)||0),100*((1-p-e)/(1-e)||0),100*e]},p.rgb.keyword=function(t){var o=e[t];if(o)return o;var p,b=1/0;for(var M in n)if(n.hasOwnProperty(M)){var z=r(t,n[M]);z.04045?Math.pow((e+.055)/1.055,2.4):e/12.92)+.3576*(o=o>.04045?Math.pow((o+.055)/1.055,2.4):o/12.92)+.1805*(n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92)),100*(.2126*e+.7152*o+.0722*n),100*(.0193*e+.1192*o+.9505*n)]},p.rgb.lab=function(t){var e=p.rgb.xyz(t),o=e[0],n=e[1],b=e[2];return n/=100,b/=108.883,o=(o/=95.047)>.008856?Math.pow(o,1/3):7.787*o+16/116,[116*(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116)-16,500*(o-n),200*(n-(b=b>.008856?Math.pow(b,1/3):7.787*b+16/116))]},p.hsl.rgb=function(t){var e,o,n,p,b,M=t[0]/360,z=t[1]/100,r=t[2]/100;if(0===z)return[b=255*r,b,b];e=2*r-(o=r<.5?r*(1+z):r+z-r*z),p=[0,0,0];for(var c=0;c<3;c++)(n=M+1/3*-(c-1))<0&&n++,n>1&&n--,b=6*n<1?e+6*(o-e)*n:2*n<1?o:3*n<2?e+(o-e)*(2/3-n)*6:e,p[c]=255*b;return p},p.hsl.hsv=function(t){var e=t[0],o=t[1]/100,n=t[2]/100,p=o,b=Math.max(n,.01);return o*=(n*=2)<=1?n:2-n,p*=b<=1?b:2-b,[e,100*(0===n?2*p/(b+p):2*o/(n+o)),(n+o)/2*100]},p.hsv.rgb=function(t){var e=t[0]/60,o=t[1]/100,n=t[2]/100,p=Math.floor(e)%6,b=e-Math.floor(e),M=255*n*(1-o),z=255*n*(1-o*b),r=255*n*(1-o*(1-b));switch(n*=255,p){case 0:return[n,r,M];case 1:return[z,n,M];case 2:return[M,n,r];case 3:return[M,z,n];case 4:return[r,M,n];case 5:return[n,M,z]}},p.hsv.hsl=function(t){var e,o,n,p=t[0],b=t[1]/100,M=t[2]/100,z=Math.max(M,.01);return n=(2-b)*M,o=b*z,[p,100*(o=(o/=(e=(2-b)*z)<=1?e:2-e)||0),100*(n/=2)]},p.hwb.rgb=function(t){var e,o,n,p,b,M,z,r=t[0]/360,c=t[1]/100,i=t[2]/100,a=c+i;switch(a>1&&(c/=a,i/=a),n=6*r-(e=Math.floor(6*r)),0!=(1&e)&&(n=1-n),p=c+n*((o=1-i)-c),e){default:case 6:case 0:b=o,M=p,z=c;break;case 1:b=p,M=o,z=c;break;case 2:b=c,M=o,z=p;break;case 3:b=c,M=p,z=o;break;case 4:b=p,M=c,z=o;break;case 5:b=o,M=c,z=p}return[255*b,255*M,255*z]},p.cmyk.rgb=function(t){var e=t[0]/100,o=t[1]/100,n=t[2]/100,p=t[3]/100;return[255*(1-Math.min(1,e*(1-p)+p)),255*(1-Math.min(1,o*(1-p)+p)),255*(1-Math.min(1,n*(1-p)+p))]},p.xyz.rgb=function(t){var e,o,n,p=t[0]/100,b=t[1]/100,M=t[2]/100;return o=-.9689*p+1.8758*b+.0415*M,n=.0557*p+-.204*b+1.057*M,e=(e=3.2406*p+-1.5372*b+-.4986*M)>.0031308?1.055*Math.pow(e,1/2.4)-.055:12.92*e,o=o>.0031308?1.055*Math.pow(o,1/2.4)-.055:12.92*o,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:12.92*n,[255*(e=Math.min(Math.max(0,e),1)),255*(o=Math.min(Math.max(0,o),1)),255*(n=Math.min(Math.max(0,n),1))]},p.xyz.lab=function(t){var e=t[0],o=t[1],n=t[2];return o/=100,n/=108.883,e=(e/=95.047)>.008856?Math.pow(e,1/3):7.787*e+16/116,[116*(o=o>.008856?Math.pow(o,1/3):7.787*o+16/116)-16,500*(e-o),200*(o-(n=n>.008856?Math.pow(n,1/3):7.787*n+16/116))]},p.lab.xyz=function(t){var e,o,n,p=t[0];e=t[1]/500+(o=(p+16)/116),n=o-t[2]/200;var b=Math.pow(o,3),M=Math.pow(e,3),z=Math.pow(n,3);return o=b>.008856?b:(o-16/116)/7.787,e=M>.008856?M:(e-16/116)/7.787,n=z>.008856?z:(n-16/116)/7.787,[e*=95.047,o*=100,n*=108.883]},p.lab.lch=function(t){var e,o=t[0],n=t[1],p=t[2];return(e=360*Math.atan2(p,n)/2/Math.PI)<0&&(e+=360),[o,Math.sqrt(n*n+p*p),e]},p.lch.lab=function(t){var e,o=t[0],n=t[1];return e=t[2]/360*2*Math.PI,[o,n*Math.cos(e),n*Math.sin(e)]},p.rgb.ansi16=function(t){var e=t[0],o=t[1],n=t[2],b=1 in arguments?arguments[1]:p.rgb.hsv(t)[2];if(0===(b=Math.round(b/50)))return 30;var M=30+(Math.round(n/255)<<2|Math.round(o/255)<<1|Math.round(e/255));return 2===b&&(M+=60),M},p.hsv.ansi16=function(t){return p.rgb.ansi16(p.hsv.rgb(t),t[2])},p.rgb.ansi256=function(t){var e=t[0],o=t[1],n=t[2];return e===o&&o===n?e<8?16:e>248?231:Math.round((e-8)/247*24)+232:16+36*Math.round(e/255*5)+6*Math.round(o/255*5)+Math.round(n/255*5)},p.ansi16.rgb=function(t){var e=t%10;if(0===e||7===e)return t>50&&(e+=3.5),[e=e/10.5*255,e,e];var o=.5*(1+~~(t>50));return[(1&e)*o*255,(e>>1&1)*o*255,(e>>2&1)*o*255]},p.ansi256.rgb=function(t){if(t>=232){var e=10*(t-232)+8;return[e,e,e]}var o;return t-=16,[Math.floor(t/36)/5*255,Math.floor((o=t%36)/6)/5*255,o%6/5*255]},p.rgb.hex=function(t){var e=(((255&Math.round(t[0]))<<16)+((255&Math.round(t[1]))<<8)+(255&Math.round(t[2]))).toString(16).toUpperCase();return"000000".substring(e.length)+e},p.hex.rgb=function(t){var e=t.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i);if(!e)return[0,0,0];var o=e[0];3===e[0].length&&(o=o.split("").map((function(t){return t+t})).join(""));var n=parseInt(o,16);return[n>>16&255,n>>8&255,255&n]},p.rgb.hcg=function(t){var e,o=t[0]/255,n=t[1]/255,p=t[2]/255,b=Math.max(Math.max(o,n),p),M=Math.min(Math.min(o,n),p),z=b-M;return e=z<=0?0:b===o?(n-p)/z%6:b===n?2+(p-o)/z:4+(o-n)/z+4,e/=6,[360*(e%=1),100*z,100*(z<1?M/(1-z):0)]},p.hsl.hcg=function(t){var e=t[1]/100,o=t[2]/100,n=1,p=0;return(n=o<.5?2*e*o:2*e*(1-o))<1&&(p=(o-.5*n)/(1-n)),[t[0],100*n,100*p]},p.hsv.hcg=function(t){var e=t[1]/100,o=t[2]/100,n=e*o,p=0;return n<1&&(p=(o-n)/(1-n)),[t[0],100*n,100*p]},p.hcg.rgb=function(t){var e=t[0]/360,o=t[1]/100,n=t[2]/100;if(0===o)return[255*n,255*n,255*n];var p=[0,0,0],b=e%1*6,M=b%1,z=1-M,r=0;switch(Math.floor(b)){case 0:p[0]=1,p[1]=M,p[2]=0;break;case 1:p[0]=z,p[1]=1,p[2]=0;break;case 2:p[0]=0,p[1]=1,p[2]=M;break;case 3:p[0]=0,p[1]=z,p[2]=1;break;case 4:p[0]=M,p[1]=0,p[2]=1;break;default:p[0]=1,p[1]=0,p[2]=z}return r=(1-o)*n,[255*(o*p[0]+r),255*(o*p[1]+r),255*(o*p[2]+r)]},p.hcg.hsv=function(t){var e=t[1]/100,o=e+t[2]/100*(1-e),n=0;return o>0&&(n=e/o),[t[0],100*n,100*o]},p.hcg.hsl=function(t){var e=t[1]/100,o=t[2]/100*(1-e)+.5*e,n=0;return o>0&&o<.5?n=e/(2*o):o>=.5&&o<1&&(n=e/(2*(1-o))),[t[0],100*n,100*o]},p.hcg.hwb=function(t){var e=t[1]/100,o=e+t[2]/100*(1-e);return[t[0],100*(o-e),100*(1-o)]},p.hwb.hcg=function(t){var e=t[1]/100,o=1-t[2]/100,n=o-e,p=0;return n<1&&(p=(o-n)/(1-n)),[t[0],100*n,100*p]},p.apple.rgb=function(t){return[t[0]/65535*255,t[1]/65535*255,t[2]/65535*255]},p.rgb.apple=function(t){return[t[0]/255*65535,t[1]/255*65535,t[2]/255*65535]},p.gray.rgb=function(t){return[t[0]/100*255,t[0]/100*255,t[0]/100*255]},p.gray.hsl=p.gray.hsv=function(t){return[0,0,t[0]]},p.gray.hwb=function(t){return[0,100,t[0]]},p.gray.cmyk=function(t){return[0,0,0,t[0]]},p.gray.lab=function(t){return[t[0],0,0]},p.gray.hex=function(t){var e=255&Math.round(t[0]/100*255),o=((e<<16)+(e<<8)+e).toString(16).toUpperCase();return"000000".substring(o.length)+o},p.rgb.gray=function(t){return[(t[0]+t[1]+t[2])/3/255*100]}}));function b(){for(var t={},e=Object.keys(p),o=e.length,n=0;n1&&(e=Array.prototype.slice.call(arguments)),t(e))};return"conversion"in t&&(e.conversion=t.conversion),e}function O(t){var e=function(e){if(null==e)return e;arguments.length>1&&(e=Array.prototype.slice.call(arguments));var o=t(e);if("object"==typeof o)for(var n=o.length,p=0;p=0&&e<1?w(Math.round(255*e)):"")}function g(t,e){return e<1||t[3]&&t[3]<1?m(t,e):"rgb("+t[0]+", "+t[1]+", "+t[2]+")"}function m(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function R(t,e){return e<1||t[3]&&t[3]<1?y(t,e):"rgb("+Math.round(t[0]/255*100)+"%, "+Math.round(t[1]/255*100)+"%, "+Math.round(t[2]/255*100)+"%)"}function y(t,e){return"rgba("+Math.round(t[0]/255*100)+"%, "+Math.round(t[1]/255*100)+"%, "+Math.round(t[2]/255*100)+"%, "+(e||t[3]||1)+")"}function B(t,e){return e<1||t[3]&&t[3]<1?L(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function L(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function X(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"}function _(t){return x[t.slice(0,3)]}function N(t,e,o){return Math.min(Math.max(e,t),o)}function w(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var x={};for(var T in l)x[l[T]]=T;var C=function(t){return t instanceof C?t:this instanceof C?(this.valid=!1,this.values={rgb:[0,0,0],hsl:[0,0,0],hsv:[0,0,0],hwb:[0,0,0],cmyk:[0,0,0,0],alpha:1},void("string"==typeof t?(e=d.getRgba(t))?this.setValues("rgb",e):(e=d.getHsla(t))?this.setValues("hsl",e):(e=d.getHwb(t))&&this.setValues("hwb",e):"object"==typeof t&&(void 0!==(e=t).r||void 0!==e.red?this.setValues("rgb",e):void 0!==e.l||void 0!==e.lightness?this.setValues("hsl",e):void 0!==e.v||void 0!==e.value?this.setValues("hsv",e):void 0!==e.w||void 0!==e.whiteness?this.setValues("hwb",e):void 0===e.c&&void 0===e.cyan||this.setValues("cmyk",e)))):new C(t);var e};C.prototype={isValid:function(){return this.valid},rgb:function(){return this.setSpace("rgb",arguments)},hsl:function(){return this.setSpace("hsl",arguments)},hsv:function(){return this.setSpace("hsv",arguments)},hwb:function(){return this.setSpace("hwb",arguments)},cmyk:function(){return this.setSpace("cmyk",arguments)},rgbArray:function(){return this.values.rgb},hslArray:function(){return this.values.hsl},hsvArray:function(){return this.values.hsv},hwbArray:function(){var t=this.values;return 1!==t.alpha?t.hwb.concat([t.alpha]):t.hwb},cmykArray:function(){return this.values.cmyk},rgbaArray:function(){var t=this.values;return t.rgb.concat([t.alpha])},hslaArray:function(){var t=this.values;return t.hsl.concat([t.alpha])},alpha:function(t){return void 0===t?this.values.alpha:(this.setValues("alpha",t),this)},red:function(t){return this.setChannel("rgb",0,t)},green:function(t){return this.setChannel("rgb",1,t)},blue:function(t){return this.setChannel("rgb",2,t)},hue:function(t){return t&&(t=(t%=360)<0?360+t:t),this.setChannel("hsl",0,t)},saturation:function(t){return this.setChannel("hsl",1,t)},lightness:function(t){return this.setChannel("hsl",2,t)},saturationv:function(t){return this.setChannel("hsv",1,t)},whiteness:function(t){return this.setChannel("hwb",1,t)},blackness:function(t){return this.setChannel("hwb",2,t)},value:function(t){return this.setChannel("hsv",2,t)},cyan:function(t){return this.setChannel("cmyk",0,t)},magenta:function(t){return this.setChannel("cmyk",1,t)},yellow:function(t){return this.setChannel("cmyk",2,t)},black:function(t){return this.setChannel("cmyk",3,t)},hexString:function(){return d.hexString(this.values.rgb)},rgbString:function(){return d.rgbString(this.values.rgb,this.values.alpha)},rgbaString:function(){return d.rgbaString(this.values.rgb,this.values.alpha)},percentString:function(){return d.percentString(this.values.rgb,this.values.alpha)},hslString:function(){return d.hslString(this.values.hsl,this.values.alpha)},hslaString:function(){return d.hslaString(this.values.hsl,this.values.alpha)},hwbString:function(){return d.hwbString(this.values.hwb,this.values.alpha)},keyword:function(){return d.keyword(this.values.rgb,this.values.alpha)},rgbNumber:function(){var t=this.values.rgb;return t[0]<<16|t[1]<<8|t[2]},luminosity:function(){for(var t=this.values.rgb,e=[],o=0;oo?(e+.05)/(o+.05):(o+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb;return(299*t[0]+587*t[1]+114*t[2])/1e3<128},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;e<3;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){var e=this.values.hsl;return e[2]+=e[2]*t,this.setValues("hsl",e),this},darken:function(t){var e=this.values.hsl;return e[2]-=e[2]*t,this.setValues("hsl",e),this},saturate:function(t){var e=this.values.hsl;return e[1]+=e[1]*t,this.setValues("hsl",e),this},desaturate:function(t){var e=this.values.hsl;return e[1]-=e[1]*t,this.setValues("hsl",e),this},whiten:function(t){var e=this.values.hwb;return e[1]+=e[1]*t,this.setValues("hwb",e),this},blacken:function(t){var e=this.values.hwb;return e[2]+=e[2]*t,this.setValues("hwb",e),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){var e=this.values.alpha;return this.setValues("alpha",e-e*t),this},opaquer:function(t){var e=this.values.alpha;return this.setValues("alpha",e+e*t),this},rotate:function(t){var e=this.values.hsl,o=(e[0]+t)%360;return e[0]=o<0?360+o:o,this.setValues("hsl",e),this},mix:function(t,e){var o=this,n=t,p=void 0===e?.5:e,b=2*p-1,M=o.alpha()-n.alpha(),z=((b*M==-1?b:(b+M)/(1+b*M))+1)/2,r=1-z;return this.rgb(z*o.red()+r*n.red(),z*o.green()+r*n.green(),z*o.blue()+r*n.blue()).alpha(o.alpha()*p+n.alpha()*(1-p))},toJSON:function(){return this.rgb()},clone:function(){var t,e,o=new C,n=this.values,p=o.values;for(var b in n)n.hasOwnProperty(b)&&(t=n[b],"[object Array]"===(e={}.toString.call(t))?p[b]=t.slice(0):"[object Number]"===e&&(p[b]=t));return o}},C.prototype.spaces={rgb:["red","green","blue"],hsl:["hue","saturation","lightness"],hsv:["hue","saturation","value"],hwb:["hue","whiteness","blackness"],cmyk:["cyan","magenta","yellow","black"]},C.prototype.maxes={rgb:[255,255,255],hsl:[360,100,100],hsv:[360,100,100],hwb:[360,100,100],cmyk:[100,100,100,100]},C.prototype.getValues=function(t){for(var e=this.values,o={},n=0;n=0;p--)e.call(o,t[p],p);else for(p=0;p=1?t:-(Math.sqrt(1-t*t)-1)},easeOutCirc:function(t){return Math.sqrt(1-(t-=1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,o=0,n=1;return 0===t?0:1===t?1:(o||(o=.3),n<1?(n=1,e=o/4):e=o/(2*Math.PI)*Math.asin(1/n),-n*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/o))},easeOutElastic:function(t){var e=1.70158,o=0,n=1;return 0===t?0:1===t?1:(o||(o=.3),n<1?(n=1,e=o/4):e=o/(2*Math.PI)*Math.asin(1/n),n*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/o)+1)},easeInOutElastic:function(t){var e=1.70158,o=0,n=1;return 0===t?0:2==(t/=.5)?1:(o||(o=.45),n<1?(n=1,e=o/4):e=o/(2*Math.PI)*Math.asin(1/n),t<1?n*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/o)*-.5:n*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/o)*.5+1)},easeInBack:function(t){var e=1.70158;return t*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:function(t){return 1-j.easeOutBounce(1-t)},easeOutBounce:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},easeInOutBounce:function(t){return t<.5?.5*j.easeInBounce(2*t):.5*j.easeOutBounce(2*t-1)+.5}},I={effects:j};P.easingEffects=j;var F=Math.PI,H=F/180,U=2*F,V=F/2,$=F/4,Y=2*F/3,G={clear:function(t){t.ctx.clearRect(0,0,t.width,t.height)},roundedRect:function(t,e,o,n,p,b){if(b){var M=Math.min(b,p/2,n/2),z=e+M,r=o+M,c=e+n-M,i=o+p-M;t.moveTo(e,r),ze.left-o&&t.xe.top-o&&t.y0&&t.requestAnimationFrame()},advance:function(){for(var t,e,o,n,p=this.animations,b=0;b=o?(zt.callback(t.onAnimationComplete,[t],e),e.animating=!1,p.splice(b,1)):++b}},qt=zt.options.resolve,ht=["push","pop","shift","splice","unshift"];function Wt(t,e){t._chartjs?t._chartjs.listeners.push(e):(Object.defineProperty(t,"_chartjs",{configurable:!0,enumerable:!1,value:{listeners:[e]}}),ht.forEach((function(e){var o="onData"+e.charAt(0).toUpperCase()+e.slice(1),n=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value:function(){var e=Array.prototype.slice.call(arguments),p=n.apply(this,e);return zt.each(t._chartjs.listeners,(function(t){"function"==typeof t[o]&&t[o].apply(t,e)})),p}})})))}function vt(t,e){var o=t._chartjs;if(o){var n=o.listeners,p=n.indexOf(e);-1!==p&&n.splice(p,1),n.length>0||(ht.forEach((function(e){delete t[e]})),delete t._chartjs)}}var gt=function(t,e){this.initialize(t,e)};zt.extend(gt.prototype,{datasetElementType:null,dataElementType:null,_datasetElementOptions:["backgroundColor","borderCapStyle","borderColor","borderDash","borderDashOffset","borderJoinStyle","borderWidth"],_dataElementOptions:["backgroundColor","borderColor","borderWidth","pointStyle"],initialize:function(t,e){var o=this;o.chart=t,o.index=e,o.linkScales(),o.addElements(),o._type=o.getMeta().type},updateIndex:function(t){this.index=t},linkScales:function(){var t=this,e=t.getMeta(),o=t.chart,n=o.scales,p=t.getDataset(),b=o.options.scales;null!==e.xAxisID&&e.xAxisID in n&&!p.xAxisID||(e.xAxisID=p.xAxisID||b.xAxes[0].id),null!==e.yAxisID&&e.yAxisID in n&&!p.yAxisID||(e.yAxisID=p.yAxisID||b.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getMeta:function(){return this.chart.getDatasetMeta(this.index)},getScaleForId:function(t){return this.chart.scales[t]},_getValueScaleId:function(){return this.getMeta().yAxisID},_getIndexScaleId:function(){return this.getMeta().xAxisID},_getValueScale:function(){return this.getScaleForId(this._getValueScaleId())},_getIndexScale:function(){return this.getScaleForId(this._getIndexScaleId())},reset:function(){this._update(!0)},destroy:function(){this._data&&vt(this._data,this)},createMetaDataset:function(){var t=this,e=t.datasetElementType;return e&&new e({_chart:t.chart,_datasetIndex:t.index})},createMetaData:function(t){var e=this,o=e.dataElementType;return o&&new o({_chart:e.chart,_datasetIndex:e.index,_index:t})},addElements:function(){var t,e,o=this,n=o.getMeta(),p=o.getDataset().data||[],b=n.data;for(t=0,e=p.length;tn&&t.insertElements(n,p-n)},insertElements:function(t,e){for(var o=0;op?(b=p/e.innerRadius,t.arc(M,z,e.innerRadius-p,n+b,o-b,!0)):t.arc(M,z,p,n+Math.PI/2,o-Math.PI/2),t.closePath(),t.clip()}function Bt(t,e,o,n){var p,b=o.endAngle;for(n&&(o.endAngle=o.startAngle+Rt,yt(t,o),o.endAngle=b,o.endAngle===o.startAngle&&o.fullCircles&&(o.endAngle+=Rt,o.fullCircles--)),t.beginPath(),t.arc(o.x,o.y,o.innerRadius,o.startAngle+Rt,o.startAngle,!0),p=0;pz;)p-=Rt;for(;p=M&&p<=z,c=b>=o.innerRadius&&b<=o.outerRadius;return r&&c}return!1},getCenterPoint:function(){var t=this._view,e=(t.startAngle+t.endAngle)/2,o=(t.innerRadius+t.outerRadius)/2;return{x:t.x+Math.cos(e)*o,y:t.y+Math.sin(e)*o}},getArea:function(){var t=this._view;return Math.PI*((t.endAngle-t.startAngle)/(2*Math.PI))*(Math.pow(t.outerRadius,2)-Math.pow(t.innerRadius,2))},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,o=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*o,y:t.y+Math.sin(e)*o}},draw:function(){var t,e=this._chart.ctx,o=this._view,n="inner"===o.borderAlign?.33:0,p={x:o.x,y:o.y,innerRadius:o.innerRadius,outerRadius:Math.max(o.outerRadius-n,0),pixelMargin:n,startAngle:o.startAngle,endAngle:o.endAngle,fullCircles:Math.floor(o.circumference/Rt)};if(e.save(),e.fillStyle=o.backgroundColor,e.strokeStyle=o.borderColor,p.fullCircles){for(p.endAngle=p.startAngle+Rt,e.beginPath(),e.arc(p.x,p.y,p.outerRadius,p.startAngle,p.endAngle),e.arc(p.x,p.y,p.innerRadius,p.endAngle,p.startAngle,!0),e.closePath(),t=0;tt.x&&(e=jt(e,"left","right")):t.baseo?o:n,r:r.right||p<0?0:p>e?e:p,b:r.bottom||b<0?0:b>o?o:b,l:r.left||M<0?0:M>e?e:M}}function Ht(t){var e=Pt(t),o=e.right-e.left,n=e.bottom-e.top,p=Ft(t,o/2,n/2);return{outer:{x:e.left,y:e.top,w:o,h:n},inner:{x:e.left+p.l,y:e.top+p.t,w:o-p.l-p.r,h:n-p.t-p.b}}}function Ut(t,e,o){var n=null===e,p=null===o,b=!(!t||n&&p)&&Pt(t);return b&&(n||e>=b.left&&e<=b.right)&&(p||o>=b.top&&o<=b.bottom)}Q._set("global",{elements:{rectangle:{backgroundColor:Et,borderColor:Et,borderSkipped:"bottom",borderWidth:0}}});var Vt=dt.extend({_type:"rectangle",draw:function(){var t=this._chart.ctx,e=this._view,o=Ht(e),n=o.outer,p=o.inner;t.fillStyle=e.backgroundColor,t.fillRect(n.x,n.y,n.w,n.h),n.w===p.w&&n.h===p.h||(t.save(),t.beginPath(),t.rect(n.x,n.y,n.w,n.h),t.clip(),t.fillStyle=e.borderColor,t.rect(p.x,p.y,p.w,p.h),t.fill("evenodd"),t.restore())},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){return Ut(this._view,t,e)},inLabelRange:function(t,e){var o=this._view;return Dt(o)?Ut(o,t,null):Ut(o,null,e)},inXRange:function(t){return Ut(this._view,t,null)},inYRange:function(t){return Ut(this._view,null,t)},getCenterPoint:function(){var t,e,o=this._view;return Dt(o)?(t=o.x,e=(o.y+o.base)/2):(t=(o.x+o.base)/2,e=o.y),{x:t,y:e}},getArea:function(){var t=this._view;return Dt(t)?t.width*Math.abs(t.y-t.base):t.height*Math.abs(t.x-t.base)},tooltipPosition:function(){var t=this._view;return{x:t.x,y:t.y}}}),$t={},Yt=Xt,Gt=wt,Jt=kt,Kt=Vt;$t.Arc=Yt,$t.Line=Gt,$t.Point=Jt,$t.Rectangle=Kt;var Qt=zt._deprecated,Zt=zt.valueOrDefault;function te(t,e){var o,n,p,b,M=t._length;for(p=1,b=e.length;p0?Math.min(M,Math.abs(n-o)):M,o=n;return M}function ee(t,e,o){var n,p,b=o.barThickness,M=e.stackCount,z=e.pixels[t],r=zt.isNullOrUndef(b)?te(e.scale,e.pixels):-1;return zt.isNullOrUndef(b)?(n=r*o.categoryPercentage,p=o.barPercentage):(n=b*M,p=1),{chunk:n/M,ratio:p,start:z-n/2}}function oe(t,e,o){var n,p=e.pixels,b=p[t],M=t>0?p[t-1]:null,z=t=0&&u.min>=0?u.min:u.max,W=void 0===u.start?u.end:u.max>=0&&u.min>=0?u.max-u.min:u.min-u.max,v=d.length;if(f||void 0===f&&void 0!==q)for(n=0;n=0&&c.max>=0?c.max:c.min,(u.min<0&&b<0||u.max>=0&&b>0)&&(h+=b));return M=O.getPixelForValue(h),r=(z=O.getPixelForValue(h+W))-M,void 0!==A&&Math.abs(r)=0&&!s||W<0&&s?M-A:M+A),{size:r,base:M,head:z,center:z+r/2}},calculateBarIndexPixels:function(t,e,o,n){var p=this,b="flex"===n.barThickness?oe(e,o,n):ee(e,o,n),M=p.getStackIndex(t,p.getMeta().stack),z=b.start+b.chunk*M+b.chunk/2,r=Math.min(Zt(n.maxBarThickness,1/0),b.chunk*b.ratio);return{base:z-r/2,head:z+r/2,center:z,size:r}},draw:function(){var t=this,e=t.chart,o=t._getValueScale(),n=t.getMeta().data,p=t.getDataset(),b=n.length,M=0;for(zt.canvas.clipArea(e.ctx,e.chartArea);M=re?-ce:f<-re?ce:0)+u,h=Math.cos(f),W=Math.sin(f),v=Math.cos(q),g=Math.sin(q),m=f<=0&&q>=0||q>=ce,R=f<=ie&&q>=ie||q>=ce+ie,y=f<=-ie&&q>=-ie||q>=re+ie,B=f===-re||q>=re?-1:Math.min(h,h*d,v,v*d),L=y?-1:Math.min(W,W*d,g,g*d),X=m?1:Math.max(h,h*d,v,v*d),_=R?1:Math.max(W,W*d,g,g*d);c=(X-B)/2,i=(_-L)/2,a=-(X+B)/2,O=-(_+L)/2}for(n=0,p=l.length;n0&&!isNaN(t)?ce*(Math.abs(t)/e):0},getMaxBorderWidth:function(t){var e,o,n,p,b,M,z,r,c=this,i=0,a=c.chart;if(!t)for(e=0,o=a.data.datasets.length;e(i=z>i?z:i)?r:i);return i},setHoverStyle:function(t){var e=t._model,o=t._options,n=zt.getHoverColor;t.$previousStyle={backgroundColor:e.backgroundColor,borderColor:e.borderColor,borderWidth:e.borderWidth},e.backgroundColor=ze(o.hoverBackgroundColor,n(o.backgroundColor)),e.borderColor=ze(o.hoverBorderColor,n(o.borderColor)),e.borderWidth=ze(o.hoverBorderWidth,o.borderWidth)},_getRingWeightOffset:function(t){for(var e=0,o=0;o0&&de(c[t-1]._model,r)&&(o.controlPointPreviousX=i(o.controlPointPreviousX,r.left,r.right),o.controlPointPreviousY=i(o.controlPointPreviousY,r.top,r.bottom)),t0&&(b=t.getDatasetMeta(b[0]._datasetIndex).data),b},"x-axis":function(t,e){return Ne(t,e,{intersect:!1})},point:function(t,e){return Le(t,ye(e,t))},nearest:function(t,e,o){var n=ye(e,t);o.axis=o.axis||"xy";var p=_e(o.axis);return Xe(t,n,o.intersect,p)},x:function(t,e,o){var n=ye(e,t),p=[],b=!1;return Be(t,(function(t){t.inXRange(n.x)&&p.push(t),t.inRange(n.x,n.y)&&(b=!0)})),o.intersect&&!b&&(p=[]),p},y:function(t,e,o){var n=ye(e,t),p=[],b=!1;return Be(t,(function(t){t.inYRange(n.y)&&p.push(t),t.inRange(n.x,n.y)&&(b=!0)})),o.intersect&&!b&&(p=[]),p}}},xe=zt.extend;function Te(t,e){return zt.where(t,(function(t){return t.pos===e}))}function Ce(t,e){return t.sort((function(t,o){var n=e?o:t,p=e?t:o;return n.weight===p.weight?n.index-p.index:n.weight-p.weight}))}function Se(t){var e,o,n,p=[];for(e=0,o=(t||[]).length;e div {\r\n\tposition: absolute;\r\n\twidth: 1000000px;\r\n\theight: 1000000px;\r\n\tleft: 0;\r\n\ttop: 0;\r\n}\r\n\r\n.chartjs-size-monitor-shrink > div {\r\n\tposition: absolute;\r\n\twidth: 200%;\r\n\theight: 200%;\r\n\tleft: 0;\r\n\ttop: 0;\r\n}\r\n",Ye=o(Object.freeze({__proto__:null,default:$e})),Ge="$chartjs",Je="chartjs-",Ke=Je+"size-monitor",Qe=Je+"render-monitor",Ze=Je+"render-animation",to=["animationstart","webkitAnimationStart"],eo={touchstart:"mousedown",touchmove:"mousemove",touchend:"mouseup",pointerenter:"mouseenter",pointerdown:"mousedown",pointermove:"mousemove",pointerup:"mouseup",pointerleave:"mouseout",pointerout:"mouseout"};function oo(t,e){var o=zt.getStyle(t,e),n=o&&o.match(/^(\d+)(\.\d+)?px$/);return n?Number(n[1]):void 0}function no(t,e){var o=t.style,n=t.getAttribute("height"),p=t.getAttribute("width");if(t[Ge]={initial:{height:n,width:p,style:{display:o.display,height:o.height,width:o.width}}},o.display=o.display||"block",null===p||""===p){var b=oo(t,"width");void 0!==b&&(t.width=b)}if(null===n||""===n)if(""===t.style.height)t.height=t.width/(e.options.aspectRatio||2);else{var M=oo(t,"height");void 0!==b&&(t.height=M)}return t}var po=function(){var t=!1;try{var e=Object.defineProperty({},"passive",{get:function(){t=!0}});window.addEventListener("e",null,e)}catch(t){}return t}(),bo=!!po&&{passive:!0};function Mo(t,e,o){t.addEventListener(e,o,bo)}function zo(t,e,o){t.removeEventListener(e,o,bo)}function ro(t,e,o,n,p){return{type:t,chart:e,native:p||null,x:void 0!==o?o:null,y:void 0!==n?n:null}}function co(t,e){var o=eo[t.type]||t.type,n=zt.getRelativePosition(t,e);return ro(o,e,n.x,n.y,t)}function io(t,e){var o=!1,n=[];return function(){n=Array.prototype.slice.call(arguments),e=e||this,o||(o=!0,zt.requestAnimFrame.call(window,(function(){o=!1,t.apply(e,n)})))}}function ao(t){var e=document.createElement("div");return e.className=t||"",e}function Oo(t){var e=1e6,o=ao(Ke),n=ao(Ke+"-expand"),p=ao(Ke+"-shrink");n.appendChild(ao()),p.appendChild(ao()),o.appendChild(n),o.appendChild(p),o._reset=function(){n.scrollLeft=e,n.scrollTop=e,p.scrollLeft=e,p.scrollTop=e};var b=function(){o._reset(),t()};return Mo(n,"scroll",b.bind(n,"expand")),Mo(p,"scroll",b.bind(p,"shrink")),o}function so(t,e){var o=t[Ge]||(t[Ge]={}),n=o.renderProxy=function(t){t.animationName===Ze&&e()};zt.each(to,(function(e){Mo(t,e,n)})),o.reflow=!!t.offsetParent,t.classList.add(Qe)}function lo(t){var e=t[Ge]||{},o=e.renderProxy;o&&(zt.each(to,(function(e){zo(t,e,o)})),delete e.renderProxy),t.classList.remove(Qe)}function uo(t,e,o){var n=t[Ge]||(t[Ge]={}),p=n.resizer=Oo(io((function(){if(n.resizer){var p=o.options.maintainAspectRatio&&t.parentNode,b=p?p.clientWidth:0;e(ro("resize",o)),p&&p.clientWidth0){var b=t[0];b.label?o=b.label:b.xLabel?o=b.xLabel:p>0&&b.index-1?t.split("\n"):t}function Xo(t){var e=t._xScale,o=t._yScale||t._scale,n=t._index,p=t._datasetIndex,b=t._chart.getDatasetMeta(p).controller,M=b._getIndexScale(),z=b._getValueScale();return{xLabel:e?e.getLabelForIndex(n,p):"",yLabel:o?o.getLabelForIndex(n,p):"",label:M?""+M.getLabelForIndex(n,p):"",value:z?""+z.getLabelForIndex(n,p):"",index:n,datasetIndex:p,x:t._model.x,y:t._model.y}}function _o(t){var e=Q.global;return{xPadding:t.xPadding,yPadding:t.yPadding,xAlign:t.xAlign,yAlign:t.yAlign,rtl:t.rtl,textDirection:t.textDirection,bodyFontColor:t.bodyFontColor,_bodyFontFamily:mo(t.bodyFontFamily,e.defaultFontFamily),_bodyFontStyle:mo(t.bodyFontStyle,e.defaultFontStyle),_bodyAlign:t.bodyAlign,bodyFontSize:mo(t.bodyFontSize,e.defaultFontSize),bodySpacing:t.bodySpacing,titleFontColor:t.titleFontColor,_titleFontFamily:mo(t.titleFontFamily,e.defaultFontFamily),_titleFontStyle:mo(t.titleFontStyle,e.defaultFontStyle),titleFontSize:mo(t.titleFontSize,e.defaultFontSize),_titleAlign:t.titleAlign,titleSpacing:t.titleSpacing,titleMarginBottom:t.titleMarginBottom,footerFontColor:t.footerFontColor,_footerFontFamily:mo(t.footerFontFamily,e.defaultFontFamily),_footerFontStyle:mo(t.footerFontStyle,e.defaultFontStyle),footerFontSize:mo(t.footerFontSize,e.defaultFontSize),_footerAlign:t.footerAlign,footerSpacing:t.footerSpacing,footerMarginTop:t.footerMarginTop,caretSize:t.caretSize,cornerRadius:t.cornerRadius,backgroundColor:t.backgroundColor,opacity:0,legendColorBackground:t.multiKeyBackground,displayColors:t.displayColors,borderColor:t.borderColor,borderWidth:t.borderWidth}}function No(t,e){var o=t._chart.ctx,n=2*e.yPadding,p=0,b=e.body,M=b.reduce((function(t,e){return t+e.before.length+e.lines.length+e.after.length}),0);M+=e.beforeBody.length+e.afterBody.length;var z=e.title.length,r=e.footer.length,c=e.titleFontSize,i=e.bodyFontSize,a=e.footerFontSize;n+=z*c,n+=z?(z-1)*e.titleSpacing:0,n+=z?e.titleMarginBottom:0,n+=M*i,n+=M?(M-1)*e.bodySpacing:0,n+=r?e.footerMarginTop:0,n+=r*a,n+=r?(r-1)*e.footerSpacing:0;var O=0,s=function(t){p=Math.max(p,o.measureText(t).width+O)};return o.font=zt.fontString(c,e._titleFontStyle,e._titleFontFamily),zt.each(e.title,s),o.font=zt.fontString(i,e._bodyFontStyle,e._bodyFontFamily),zt.each(e.beforeBody.concat(e.afterBody),s),O=e.displayColors?i+2:0,zt.each(b,(function(t){zt.each(t.before,s),zt.each(t.lines,s),zt.each(t.after,s)})),O=0,o.font=zt.fontString(a,e._footerFontStyle,e._footerFontFamily),zt.each(e.footer,s),{width:p+=2*e.xPadding,height:n}}function wo(t,e){var o,n,p,b,M,z=t._model,r=t._chart,c=t._chart.chartArea,i="center",a="center";z.yr.height-e.height&&(a="bottom");var O=(c.left+c.right)/2,s=(c.top+c.bottom)/2;"center"===a?(o=function(t){return t<=O},n=function(t){return t>O}):(o=function(t){return t<=e.width/2},n=function(t){return t>=r.width-e.width/2}),p=function(t){return t+e.width+z.caretSize+z.caretPadding>r.width},b=function(t){return t-e.width-z.caretSize-z.caretPadding<0},M=function(t){return t<=s?"top":"bottom"},o(z.x)?(i="left",p(z.x)&&(i="center",a=M(z.y))):n(z.x)&&(i="right",b(z.x)&&(i="center",a=M(z.y)));var l=t._options;return{xAlign:l.xAlign?l.xAlign:i,yAlign:l.yAlign?l.yAlign:a}}function xo(t,e,o,n){var p=t.x,b=t.y,M=t.caretSize,z=t.caretPadding,r=t.cornerRadius,c=o.xAlign,i=o.yAlign,a=M+z,O=r+z;return"right"===c?p-=e.width:"center"===c&&((p-=e.width/2)+e.width>n.width&&(p=n.width-e.width),p<0&&(p=0)),"top"===i?b+=a:b-="bottom"===i?e.height+a:e.height/2,"center"===i?"left"===c?p+=a:"right"===c&&(p-=a):"left"===c?p-=O:"right"===c&&(p+=O),{x:p,y:b}}function To(t,e){return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-t.xPadding:t.x+t.xPadding}function Co(t){return Bo([],Lo(t))}var So=dt.extend({initialize:function(){this._model=_o(this._options),this._lastActive=[]},getTitle:function(){var t=this,e=t._options.callbacks,o=e.beforeTitle.apply(t,arguments),n=e.title.apply(t,arguments),p=e.afterTitle.apply(t,arguments),b=[];return b=Bo(b,Lo(o)),b=Bo(b,Lo(n)),b=Bo(b,Lo(p))},getBeforeBody:function(){return Co(this._options.callbacks.beforeBody.apply(this,arguments))},getBody:function(t,e){var o=this,n=o._options.callbacks,p=[];return zt.each(t,(function(t){var b={before:[],lines:[],after:[]};Bo(b.before,Lo(n.beforeLabel.call(o,t,e))),Bo(b.lines,n.label.call(o,t,e)),Bo(b.after,Lo(n.afterLabel.call(o,t,e))),p.push(b)})),p},getAfterBody:function(){return Co(this._options.callbacks.afterBody.apply(this,arguments))},getFooter:function(){var t=this,e=t._options.callbacks,o=e.beforeFooter.apply(t,arguments),n=e.footer.apply(t,arguments),p=e.afterFooter.apply(t,arguments),b=[];return b=Bo(b,Lo(o)),b=Bo(b,Lo(n)),b=Bo(b,Lo(p))},update:function(t){var e,o,n=this,p=n._options,b=n._model,M=n._model=_o(p),z=n._active,r=n._data,c={xAlign:b.xAlign,yAlign:b.yAlign},i={x:b.x,y:b.y},a={width:b.width,height:b.height},O={x:b.caretX,y:b.caretY};if(z.length){M.opacity=1;var s=[],l=[];O=yo[p.position].call(n,z,n._eventPosition);var d=[];for(e=0,o=z.length;e0&&o.stroke()},draw:function(){var t=this._chart.ctx,e=this._view;if(0!==e.opacity){var o={width:e.width,height:e.height},n={x:e.x,y:e.y},p=Math.abs(e.opacity<.001)?0:e.opacity,b=e.title.length||e.beforeBody.length||e.body.length||e.afterBody.length||e.footer.length;this._options.enabled&&b&&(t.save(),t.globalAlpha=p,this.drawBackground(n,e,t,o),n.y+=e.yPadding,zt.rtl.overrideTextDirection(t,e.textDirection),this.drawTitle(n,e,t),this.drawBody(n,e,t),this.drawFooter(n,e,t),zt.rtl.restoreTextDirection(t,e.textDirection),t.restore())}},handleEvent:function(t){var e=this,o=e._options,n=!1;return e._lastActive=e._lastActive||[],"mouseout"===t.type?e._active=[]:(e._active=e._chart.getElementsAtEventForMode(t,o.mode,o),o.reverse&&e._active.reverse()),(n=!zt.arrayEquals(e._active,e._lastActive))&&(e._lastActive=e._active,(o.enabled||o.custom)&&(e._eventPosition={x:t.x,y:t.y},e.update(!0),e.pivot())),n}}),ko=yo,Eo=So;Eo.positioners=ko;var Do=zt.valueOrDefault;function Po(){return zt.merge(Object.create(null),[].slice.call(arguments),{merger:function(t,e,o,n){if("xAxes"===t||"yAxes"===t){var p,b,M,z=o[t].length;for(e[t]||(e[t]=[]),p=0;p=e[t].length&&e[t].push({}),!e[t][p].type||M.type&&M.type!==e[t][p].type?zt.merge(e[t][p],[go.getScaleDefaults(b),M]):zt.merge(e[t][p],M)}else zt._merger(t,e,o,n)}})}function jo(){return zt.merge(Object.create(null),[].slice.call(arguments),{merger:function(t,e,o,n){var p=e[t]||Object.create(null),b=o[t];"scales"===t?e[t]=Po(p,b):"scale"===t?e[t]=zt.merge(p,[go.getScaleDefaults(b.type),b]):zt._merger(t,e,o,n)}})}function Io(t){var e=(t=t||Object.create(null)).data=t.data||{};return e.datasets=e.datasets||[],e.labels=e.labels||[],t.options=jo(Q.global,Q[t.type],t.options||{}),t}function Fo(t){var e=t.options;zt.each(t.scales,(function(e){Ue.removeBox(t,e)})),e=jo(Q.global,Q[t.config.type],e),t.options=t.config.options=e,t.ensureScalesHaveIDs(),t.buildOrUpdateScales(),t.tooltip._options=e.tooltips,t.tooltip.initialize()}function Ho(t,e,o){var n,p=function(t){return t.id===n};do{n=e+o++}while(zt.findIndex(t,p)>=0);return n}function Uo(t){return"top"===t||"bottom"===t}function Vo(t,e){return function(o,n){return o[t]===n[t]?o[e]-n[e]:o[t]-n[t]}}Q._set("global",{elements:{},events:["mousemove","mouseout","click","touchstart","touchmove"],hover:{onHover:null,mode:"nearest",intersect:!0,animationDuration:400},onClick:null,maintainAspectRatio:!0,responsive:!0,responsiveAnimationDuration:0});var $o=function(t,e){return this.construct(t,e),this};zt.extend($o.prototype,{construct:function(t,e){var o=this;e=Io(e);var n=Wo.acquireContext(t,e),p=n&&n.canvas,b=p&&p.height,M=p&&p.width;o.id=zt.uid(),o.ctx=n,o.canvas=p,o.config=e,o.width=M,o.height=b,o.aspectRatio=b?M/b:null,o.options=e.options,o._bufferedRender=!1,o._layers=[],o.chart=o,o.controller=o,$o.instances[o.id]=o,Object.defineProperty(o,"data",{get:function(){return o.config.data},set:function(t){o.config.data=t}}),n&&p&&(o.initialize(),o.update())},initialize:function(){var t=this;return vo.notify(t,"beforeInit"),zt.retinaScale(t,t.options.devicePixelRatio),t.bindEvents(),t.options.responsive&&t.resize(!0),t.initToolTip(),vo.notify(t,"afterInit"),t},clear:function(){return zt.canvas.clear(this),this},stop:function(){return ft.cancelAnimation(this),this},resize:function(t){var e=this,o=e.options,n=e.canvas,p=o.maintainAspectRatio&&e.aspectRatio||null,b=Math.max(0,Math.floor(zt.getMaximumWidth(n))),M=Math.max(0,Math.floor(p?b/p:zt.getMaximumHeight(n)));if((e.width!==b||e.height!==M)&&(n.width=e.width=b,n.height=e.height=M,n.style.width=b+"px",n.style.height=M+"px",zt.retinaScale(e,o.devicePixelRatio),!t)){var z={width:b,height:M};vo.notify(e,"resize",[z]),o.onResize&&o.onResize(e,z),e.stop(),e.update({duration:o.responsiveAnimationDuration})}},ensureScalesHaveIDs:function(){var t=this.options,e=t.scales||{},o=t.scale;zt.each(e.xAxes,(function(t,o){t.id||(t.id=Ho(e.xAxes,"x-axis-",o))})),zt.each(e.yAxes,(function(t,o){t.id||(t.id=Ho(e.yAxes,"y-axis-",o))})),o&&(o.id=o.id||"scale")},buildOrUpdateScales:function(){var t=this,e=t.options,o=t.scales||{},n=[],p=Object.keys(o).reduce((function(t,e){return t[e]=!1,t}),{});e.scales&&(n=n.concat((e.scales.xAxes||[]).map((function(t){return{options:t,dtype:"category",dposition:"bottom"}})),(e.scales.yAxes||[]).map((function(t){return{options:t,dtype:"linear",dposition:"left"}})))),e.scale&&n.push({options:e.scale,dtype:"radialLinear",isDefault:!0,dposition:"chartArea"}),zt.each(n,(function(e){var n=e.options,b=n.id,M=Do(n.type,e.dtype);Uo(n.position)!==Uo(e.dposition)&&(n.position=e.dposition),p[b]=!0;var z=null;if(b in o&&o[b].type===M)(z=o[b]).options=n,z.ctx=t.ctx,z.chart=t;else{var r=go.getScaleConstructor(M);if(!r)return;z=new r({id:b,type:M,options:n,ctx:t.ctx,chart:t}),o[z.id]=z}z.mergeTicksOptions(),e.isDefault&&(t.scale=z)})),zt.each(p,(function(t,e){t||delete o[e]})),t.scales=o,go.addScalesToLayout(this)},buildOrUpdateControllers:function(){var t,e,o=this,n=[],p=o.data.datasets;for(t=0,e=p.length;t=0;--o)n.drawDataset(e[o],t);vo.notify(n,"afterDatasetsDraw",[t])}},drawDataset:function(t,e){var o=this,n={meta:t,index:t.index,easingValue:e};!1!==vo.notify(o,"beforeDatasetDraw",[n])&&(t.controller.draw(e),vo.notify(o,"afterDatasetDraw",[n]))},_drawTooltip:function(t){var e=this,o=e.tooltip,n={tooltip:o,easingValue:t};!1!==vo.notify(e,"beforeTooltipDraw",[n])&&(o.draw(),vo.notify(e,"afterTooltipDraw",[n]))},getElementAtEvent:function(t){return we.modes.single(this,t)},getElementsAtEvent:function(t){return we.modes.label(this,t,{intersect:!0})},getElementsAtXAxis:function(t){return we.modes["x-axis"](this,t,{intersect:!0})},getElementsAtEventForMode:function(t,e,o){var n=we.modes[e];return"function"==typeof n?n(this,t,o):[]},getDatasetAtEvent:function(t){return we.modes.dataset(this,t,{intersect:!0})},getDatasetMeta:function(t){var e=this,o=e.data.datasets[t];o._meta||(o._meta={});var n=o._meta[e.id];return n||(n=o._meta[e.id]={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:o.order||0,index:t}),n},getVisibleDatasetCount:function(){for(var t=0,e=0,o=this.data.datasets.length;e=0;n--){var p=t[n];if(e(p))return p}},zt.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},zt.almostEquals=function(t,e,o){return Math.abs(t-e)=t},zt.max=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.max(t,e)}),Number.NEGATIVE_INFINITY)},zt.min=function(t){return t.reduce((function(t,e){return isNaN(e)?t:Math.min(t,e)}),Number.POSITIVE_INFINITY)},zt.sign=Math.sign?function(t){return Math.sign(t)}:function(t){return 0==(t=+t)||isNaN(t)?t:t>0?1:-1},zt.toRadians=function(t){return t*(Math.PI/180)},zt.toDegrees=function(t){return t*(180/Math.PI)},zt._decimalPlaces=function(t){if(zt.isFinite(t)){for(var e=1,o=0;Math.round(t*e)/e!==t;)e*=10,o++;return o}},zt.getAngleFromPoint=function(t,e){var o=e.x-t.x,n=e.y-t.y,p=Math.sqrt(o*o+n*n),b=Math.atan2(n,o);return b<-.5*Math.PI&&(b+=2*Math.PI),{angle:b,distance:p}},zt.distanceBetweenPoints=function(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))},zt.aliasPixel=function(t){return t%2==0?0:.5},zt._alignPixel=function(t,e,o){var n=t.currentDevicePixelRatio,p=o/2;return Math.round((e-p)*n)/n+p},zt.splineCurve=function(t,e,o,n){var p=t.skip?e:t,b=e,M=o.skip?e:o,z=Math.sqrt(Math.pow(b.x-p.x,2)+Math.pow(b.y-p.y,2)),r=Math.sqrt(Math.pow(M.x-b.x,2)+Math.pow(M.y-b.y,2)),c=z/(z+r),i=r/(z+r),a=n*(c=isNaN(c)?0:c),O=n*(i=isNaN(i)?0:i);return{previous:{x:b.x-a*(M.x-p.x),y:b.y-a*(M.y-p.y)},next:{x:b.x+O*(M.x-p.x),y:b.y+O*(M.y-p.y)}}},zt.EPSILON=Number.EPSILON||1e-14,zt.splineCurveMonotone=function(t){var e,o,n,p,b,M,z,r,c,i=(t||[]).map((function(t){return{model:t._model,deltaK:0,mK:0}})),a=i.length;for(e=0;e0?i[e-1]:null,(p=e0?i[e-1]:null,p=e=t.length-1?t[0]:t[e+1]:e>=t.length-1?t[t.length-1]:t[e+1]},zt.previousItem=function(t,e,o){return o?e<=0?t[t.length-1]:t[e-1]:e<=0?t[0]:t[e-1]},zt.niceNum=function(t,e){var o=Math.floor(zt.log10(t)),n=t/Math.pow(10,o);return(e?n<1.5?1:n<3?2:n<7?5:10:n<=1?1:n<=2?2:n<=5?5:10)*Math.pow(10,o)},zt.requestAnimFrame="undefined"==typeof window?function(t){t()}:window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)},zt.getRelativePosition=function(t,e){var o,n,p=t.originalEvent||t,b=t.target||t.srcElement,M=b.getBoundingClientRect(),z=p.touches;z&&z.length>0?(o=z[0].clientX,n=z[0].clientY):(o=p.clientX,n=p.clientY);var r=parseFloat(zt.getStyle(b,"padding-left")),c=parseFloat(zt.getStyle(b,"padding-top")),i=parseFloat(zt.getStyle(b,"padding-right")),a=parseFloat(zt.getStyle(b,"padding-bottom")),O=M.right-M.left-r-i,s=M.bottom-M.top-c-a;return{x:o=Math.round((o-M.left-r)/O*b.width/e.currentDevicePixelRatio),y:n=Math.round((n-M.top-c)/s*b.height/e.currentDevicePixelRatio)}},zt.getConstraintWidth=function(t){return o(t,"max-width","clientWidth")},zt.getConstraintHeight=function(t){return o(t,"max-height","clientHeight")},zt._calculatePadding=function(t,e,o){return(e=zt.getStyle(t,e)).indexOf("%")>-1?o*parseInt(e,10)/100:parseInt(e,10)},zt._getParentNode=function(t){var e=t.parentNode;return e&&"[object ShadowRoot]"===e.toString()&&(e=e.host),e},zt.getMaximumWidth=function(t){var e=zt._getParentNode(t);if(!e)return t.clientWidth;var o=e.clientWidth,n=o-zt._calculatePadding(e,"padding-left",o)-zt._calculatePadding(e,"padding-right",o),p=zt.getConstraintWidth(t);return isNaN(p)?n:Math.min(n,p)},zt.getMaximumHeight=function(t){var e=zt._getParentNode(t);if(!e)return t.clientHeight;var o=e.clientHeight,n=o-zt._calculatePadding(e,"padding-top",o)-zt._calculatePadding(e,"padding-bottom",o),p=zt.getConstraintHeight(t);return isNaN(p)?n:Math.min(n,p)},zt.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},zt.retinaScale=function(t,e){var o=t.currentDevicePixelRatio=e||"undefined"!=typeof window&&window.devicePixelRatio||1;if(1!==o){var n=t.canvas,p=t.height,b=t.width;n.height=p*o,n.width=b*o,t.ctx.scale(o,o),n.style.height||n.style.width||(n.style.height=p+"px",n.style.width=b+"px")}},zt.fontString=function(t,e,o){return e+" "+t+"px "+o},zt.longestText=function(t,e,o,n){var p=(n=n||{}).data=n.data||{},b=n.garbageCollect=n.garbageCollect||[];n.font!==e&&(p=n.data={},b=n.garbageCollect=[],n.font=e),t.font=e;var M,z,r,c,i,a=0,O=o.length;for(M=0;Mo.length){for(M=0;Mn&&(n=b),n},zt.numberOfLabelLines=function(t){var e=1;return zt.each(t,(function(t){zt.isArray(t)&&t.length>e&&(e=t.length)})),e},zt.color=S?function(t){return t instanceof CanvasGradient&&(t=Q.global.defaultColor),S(t)}:function(t){return t},zt.getHoverColor=function(t){return t instanceof CanvasPattern||t instanceof CanvasGradient?t:zt.color(t).saturate(.5).darken(.1).rgbString()}};function Jo(){throw new Error("This method is not implemented: either no adapter can be found or an incomplete integration was provided.")}function Ko(t){this.options=t||{}}zt.extend(Ko.prototype,{formats:Jo,parse:Jo,format:Jo,add:Jo,diff:Jo,startOf:Jo,endOf:Jo,_create:function(t){return t}}),Ko.override=function(t){zt.extend(Ko.prototype,t)};var Qo={_date:Ko},Zo={formatters:{values:function(t){return zt.isArray(t)?t:""+t},linear:function(t,e,o){var n=o.length>3?o[2]-o[1]:o[1]-o[0];Math.abs(n)>1&&t!==Math.floor(t)&&(n=t-Math.floor(t));var p=zt.log10(Math.abs(n)),b="";if(0!==t)if(Math.max(Math.abs(o[0]),Math.abs(o[o.length-1]))<1e-4){var M=zt.log10(Math.abs(t)),z=Math.floor(M)-Math.floor(p);z=Math.max(Math.min(z,20),0),b=t.toExponential(z)}else{var r=-1*Math.floor(p);r=Math.max(Math.min(r,20),0),b=t.toFixed(r)}else b="0";return b},logarithmic:function(t,e,o){var n=t/Math.pow(10,Math.floor(zt.log10(t)));return 0===t?"0":1===n||2===n||5===n||0===e||e===o.length-1?t.toExponential():""}}},tn=zt.isArray,en=zt.isNullOrUndef,on=zt.valueOrDefault,nn=zt.valueAtIndexOrDefault;function pn(t,e){for(var o=[],n=t.length/e,p=0,b=t.length;pr+c)))return M}function Mn(t,e){zt.each(t,(function(t){var o,n=t.gc,p=n.length/2;if(p>e){for(o=0;oc)return b;return Math.max(c,1)}function un(t){var e,o,n=[];for(e=0,o=t.length;e=O||i<=1||!z.isHorizontal()?z.labelRotation=a:(e=(t=z._getLabelSizes()).widest.width,o=t.highest.height-t.highest.offset,n=Math.min(z.maxWidth,z.chart.width-e),e+6>(p=r.offset?z.maxWidth/i:n/(i-1))&&(p=n/(i-(r.offset?.5:1)),b=z.maxHeight-rn(r.gridLines)-c.padding-cn(r.scaleLabel),M=Math.sqrt(e*e+o*o),s=zt.toDegrees(Math.min(Math.asin(Math.min((t.highest.height+6)/p,1)),Math.asin(Math.min(b/M,1))-Math.asin(o/M))),s=Math.max(a,Math.min(O,s))),z.labelRotation=s)},afterCalculateTickRotation:function(){zt.callback(this.options.afterCalculateTickRotation,[this])},beforeFit:function(){zt.callback(this.options.beforeFit,[this])},fit:function(){var t=this,e=t.minSize={width:0,height:0},o=t.chart,n=t.options,p=n.ticks,b=n.scaleLabel,M=n.gridLines,z=t._isVisible(),r="bottom"===n.position,c=t.isHorizontal();if(c?e.width=t.maxWidth:z&&(e.width=rn(M)+cn(b)),c?z&&(e.height=rn(M)+cn(b)):e.height=t.maxHeight,p.display&&z){var i=On(p),a=t._getLabelSizes(),O=a.first,s=a.last,l=a.widest,d=a.highest,u=.4*i.minor.lineHeight,A=p.padding;if(c){var f=0!==t.labelRotation,q=zt.toRadians(t.labelRotation),h=Math.cos(q),W=Math.sin(q),v=W*l.width+h*(d.height-(f?d.offset:0))+(f?0:u);e.height=Math.min(t.maxHeight,e.height+v+A);var g,m,R=t.getPixelForTick(0)-t.left,y=t.right-t.getPixelForTick(t.getTicks().length-1);f?(g=r?h*O.width+W*O.offset:W*(O.height-O.offset),m=r?W*(s.height-s.offset):h*s.width+W*s.offset):(g=O.width/2,m=s.width/2),t.paddingLeft=Math.max((g-R)*t.width/(t.width-R),0)+3,t.paddingRight=Math.max((m-y)*t.width/(t.width-y),0)+3}else{var B=p.mirror?0:l.width+A+u;e.width=Math.min(t.maxWidth,e.width+B),t.paddingTop=O.height/2,t.paddingBottom=s.height/2}}t.handleMargins(),c?(t.width=t._length=o.width-t.margins.left-t.margins.right,t.height=e.height):(t.width=e.width,t.height=t._length=o.height-t.margins.top-t.margins.bottom)},handleMargins:function(){var t=this;t.margins&&(t.margins.left=Math.max(t.paddingLeft,t.margins.left),t.margins.top=Math.max(t.paddingTop,t.margins.top),t.margins.right=Math.max(t.paddingRight,t.margins.right),t.margins.bottom=Math.max(t.paddingBottom,t.margins.bottom))},afterFit:function(){zt.callback(this.options.afterFit,[this])},isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},isFullWidth:function(){return this.options.fullWidth},getRightValue:function(t){if(en(t))return NaN;if(("number"==typeof t||t instanceof Number)&&!isFinite(t))return NaN;if(t)if(this.isHorizontal()){if(void 0!==t.x)return this.getRightValue(t.x)}else if(void 0!==t.y)return this.getRightValue(t.y);return t},_convertTicksToLabels:function(t){var e,o,n,p=this;for(p.ticks=t.map((function(t){return t.value})),p.beforeTickToLabelConversion(),e=p.convertTicksToLabels(t)||p.ticks,p.afterTickToLabelConversion(),o=0,n=t.length;on-1?null:e.getPixelForDecimal(t*p+(o?p/2:0))},getPixelForDecimal:function(t){var e=this;return e._reversePixels&&(t=1-t),e._startPixel+t*e._length},getDecimalForPixel:function(t){var e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e},getBasePixel:function(){return this.getPixelForValue(this.getBaseValue())},getBaseValue:function(){var t=this,e=t.min,o=t.max;return t.beginAtZero?0:e<0&&o<0?o:e>0&&o>0?e:0},_autoSkip:function(t){var e,o,n,p,b=this,M=b.options.ticks,z=b._length,r=M.maxTicksLimit||z/b._tickSize()+1,c=M.major.enabled?un(t):[],i=c.length,a=c[0],O=c[i-1];if(i>r)return An(t,c,i/r),sn(t);if(n=dn(c,t,z,r),i>0){for(e=0,o=i-1;e1?(O-a)/(i-1):null,fn(t,n,zt.isNullOrUndef(p)?0:a-p,a),fn(t,n,O,zt.isNullOrUndef(p)?t.length:O+p),sn(t)}return fn(t,n),sn(t)},_tickSize:function(){var t=this,e=t.options.ticks,o=zt.toRadians(t.labelRotation),n=Math.abs(Math.cos(o)),p=Math.abs(Math.sin(o)),b=t._getLabelSizes(),M=e.autoSkipPadding||0,z=b?b.widest.width+M:0,r=b?b.highest.height+M:0;return t.isHorizontal()?r*n>z*p?z/n:r/p:r*p=0&&(M=t),void 0!==b&&(t=o.indexOf(b))>=0&&(z=t),e.minIndex=M,e.maxIndex=z,e.min=o[M],e.max=o[z]},buildTicks:function(){var t=this,e=t._getLabels(),o=t.minIndex,n=t.maxIndex;t.ticks=0===o&&n===e.length-1?e:e.slice(o,n+1)},getLabelForIndex:function(t,e){var o=this,n=o.chart;return n.getDatasetMeta(e).controller._getValueScaleId()===o.id?o.getRightValue(n.data.datasets[e].data[t]):o._getLabels()[t]},_configure:function(){var t=this,e=t.options.offset,o=t.ticks;hn.prototype._configure.call(t),t.isHorizontal()||(t._reversePixels=!t._reversePixels),o&&(t._startValue=t.minIndex-(e?.5:0),t._valueRange=Math.max(o.length-(e?0:1),1))},getPixelForValue:function(t,e,o){var n,p,b,M=this;return Wn(e)||Wn(o)||(t=M.chart.data.datasets[o].data[e]),Wn(t)||(n=M.isHorizontal()?t.x:t.y),(void 0!==n||void 0!==t&&isNaN(e))&&(p=M._getLabels(),t=zt.valueOrDefault(n,t),e=-1!==(b=p.indexOf(t))?b:e,isNaN(e)&&(e=t)),M.getPixelForDecimal((e-M._startValue)/M._valueRange)},getPixelForTick:function(t){var e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t],t+this.minIndex)},getValueForPixel:function(t){var e=this,o=Math.round(e._startValue+e.getDecimalForPixel(t)*e._valueRange);return Math.min(Math.max(o,0),e.ticks.length-1)},getBasePixel:function(){return this.bottom}}),mn=vn;gn._defaults=mn;var Rn=zt.noop,yn=zt.isNullOrUndef;function Bn(t,e){var o,n,p,b,M=[],z=1e-14,r=t.stepSize,c=r||1,i=t.maxTicks-1,a=t.min,O=t.max,s=t.precision,l=e.min,d=e.max,u=zt.niceNum((d-l)/i/c)*c;if(ui&&(u=zt.niceNum(b*u/i/c)*c),r||yn(s)?o=Math.pow(10,zt._decimalPlaces(u)):(o=Math.pow(10,s),u=Math.ceil(u*o)/o),n=Math.floor(l/u)*u,p=Math.ceil(d/u)*u,r&&(!yn(a)&&zt.almostWhole(a/u,u/1e3)&&(n=a),!yn(O)&&zt.almostWhole(O/u,u/1e3)&&(p=O)),b=(p-n)/u,b=zt.almostEquals(b,Math.round(b),u/1e3)?Math.round(b):Math.ceil(b),n=Math.round(n*o)/o,p=Math.round(p*o)/o,M.push(yn(a)?n:a);for(var A=1;A0&&n>0&&(t.min=0)}var p=void 0!==e.min||void 0!==e.suggestedMin,b=void 0!==e.max||void 0!==e.suggestedMax;void 0!==e.min?t.min=e.min:void 0!==e.suggestedMin&&(null===t.min?t.min=e.suggestedMin:t.min=Math.min(t.min,e.suggestedMin)),void 0!==e.max?t.max=e.max:void 0!==e.suggestedMax&&(null===t.max?t.max=e.suggestedMax:t.max=Math.max(t.max,e.suggestedMax)),p!==b&&t.min>=t.max&&(p?t.max=t.min+1:t.min=t.max-1),t.min===t.max&&(t.max++,e.beginAtZero||t.min--)},getTickLimit:function(){var t,e=this,o=e.options.ticks,n=o.stepSize,p=o.maxTicksLimit;return n?t=Math.ceil(e.max/n)-Math.floor(e.min/n)+1:(t=e._computeTickLimit(),p=p||11),p&&(t=Math.min(p,t)),t},_computeTickLimit:function(){return Number.POSITIVE_INFINITY},handleDirectionalChanges:Rn,buildTicks:function(){var t=this,e=t.options.ticks,o=t.getTickLimit(),n={maxTicks:o=Math.max(2,o),min:e.min,max:e.max,precision:e.precision,stepSize:zt.valueOrDefault(e.fixedStepSize,e.stepSize)},p=t.ticks=Bn(n,t);t.handleDirectionalChanges(),t.max=zt.max(p),t.min=zt.min(p),e.reverse?(p.reverse(),t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max)},convertTicksToLabels:function(){var t=this;t.ticksAsNumbers=t.ticks.slice(),t.zeroLineIndex=t.ticks.indexOf(0),hn.prototype.convertTicksToLabels.call(t)},_configure:function(){var t,e=this,o=e.getTicks(),n=e.min,p=e.max;hn.prototype._configure.call(e),e.options.offset&&o.length&&(n-=t=(p-n)/Math.max(o.length-1,1)/2,p+=t),e._startValue=n,e._endValue=p,e._valueRange=p-n}}),Xn={position:"left",ticks:{callback:Zo.formatters.linear}},_n=0,Nn=1;function wn(t,e,o){var n=[o.type,void 0===e&&void 0===o.stack?o.index:"",o.stack].join(".");return void 0===t[n]&&(t[n]={pos:[],neg:[]}),t[n]}function xn(t,e,o,n){var p,b,M=t.options,z=wn(e,M.stacked,o),r=z.pos,c=z.neg,i=n.length;for(p=0;pe.length-1?null:this.getPixelForValue(e[t])}}),Sn=Xn;Cn._defaults=Sn;var kn=zt.valueOrDefault,En=zt.math.log10;function Dn(t,e){var o,n,p=[],b=kn(t.min,Math.pow(10,Math.floor(En(e.min)))),M=Math.floor(En(e.max)),z=Math.ceil(e.max/Math.pow(10,M));0===b?(o=Math.floor(En(e.minNotZero)),n=Math.floor(e.minNotZero/Math.pow(10,o)),p.push(b),b=n*Math.pow(10,o)):(o=Math.floor(En(b)),n=Math.floor(b/Math.pow(10,o)));var r=o<0?Math.pow(10,Math.abs(o)):1;do{p.push(b),10==++n&&(n=1,r=++o>=0?1:r),b=Math.round(n*Math.pow(10,o)*r)/r}while(o=0?t:e}var In=hn.extend({determineDataLimits:function(){var t,e,o,n,p,b,M=this,z=M.options,r=M.chart,c=r.data.datasets,i=M.isHorizontal();function a(t){return i?t.xAxisID===M.id:t.yAxisID===M.id}M.min=Number.POSITIVE_INFINITY,M.max=Number.NEGATIVE_INFINITY,M.minNotZero=Number.POSITIVE_INFINITY;var O=z.stacked;if(void 0===O)for(t=0;t0){var e=zt.min(t),o=zt.max(t);M.min=Math.min(M.min,e),M.max=Math.max(M.max,o)}}))}else for(t=0;t0?t.minNotZero=t.min:t.max<1?t.minNotZero=Math.pow(10,Math.floor(En(t.max))):t.minNotZero=o)},buildTicks:function(){var t=this,e=t.options.ticks,o=!t.isHorizontal(),n={min:jn(e.min),max:jn(e.max)},p=t.ticks=Dn(n,t);t.max=zt.max(p),t.min=zt.min(p),e.reverse?(o=!o,t.start=t.max,t.end=t.min):(t.start=t.min,t.end=t.max),o&&p.reverse()},convertTicksToLabels:function(){this.tickValues=this.ticks.slice(),hn.prototype.convertTicksToLabels.call(this)},getLabelForIndex:function(t,e){return this._getScaleLabel(this.chart.data.datasets[e].data[t])},getPixelForTick:function(t){var e=this.tickValues;return t<0||t>e.length-1?null:this.getPixelForValue(e[t])},_getFirstTickValue:function(t){var e=Math.floor(En(t));return Math.floor(t/Math.pow(10,e))*Math.pow(10,e)},_configure:function(){var t=this,e=t.min,o=0;hn.prototype._configure.call(t),0===e&&(e=t._getFirstTickValue(t.minNotZero),o=kn(t.options.ticks.fontSize,Q.global.defaultFontSize)/t._length),t._startValue=En(e),t._valueOffset=o,t._valueRange=(En(t.max)-En(e))/(1-o)},getPixelForValue:function(t){var e=this,o=0;return(t=+e.getRightValue(t))>e.min&&t>0&&(o=(En(t)-e._startValue)/e._valueRange+e._valueOffset),e.getPixelForDecimal(o)},getValueForPixel:function(t){var e=this,o=e.getDecimalForPixel(t);return 0===o&&0===e.min?0:Math.pow(10,e._startValue+(o-e._valueOffset)*e._valueRange)}}),Fn=Pn;In._defaults=Fn;var Hn=zt.valueOrDefault,Un=zt.valueAtIndexOrDefault,Vn=zt.options.resolve,$n={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,color:"rgba(0,0,0,0.1)",lineWidth:1,borderDash:[],borderDashOffset:0},gridLines:{circular:!1},ticks:{showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2,callback:Zo.formatters.linear},pointLabels:{display:!0,fontSize:10,callback:function(t){return t}}};function Yn(t){var e=t.ticks;return e.display&&t.display?Hn(e.fontSize,Q.global.defaultFontSize)+2*e.backdropPaddingY:0}function Gn(t,e,o){return zt.isArray(o)?{w:zt.longestText(t,t.font,o),h:o.length*e}:{w:t.measureText(o).width,h:e}}function Jn(t,e,o,n,p){return t===n||t===p?{start:e-o/2,end:e+o/2}:tp?{start:e-o,end:e}:{start:e,end:e+o}}function Kn(t){var e,o,n,p=zt.options._parseFont(t.options.pointLabels),b={l:0,r:t.width,t:0,b:t.height-t.paddingTop},M={};t.ctx.font=p.string,t._pointLabelSizes=[];var z=t.chart.data.labels.length;for(e=0;eb.r&&(b.r=i.end,M.r=r),a.startb.b&&(b.b=a.end,M.b=r)}t.setReductions(t.drawingArea,b,M)}function Qn(t){return 0===t||180===t?"center":t<180?"left":"right"}function Zn(t,e,o,n){var p,b,M=o.y+n/2;if(zt.isArray(e))for(p=0,b=e.length;p270||t<90)&&(o.y-=e.h)}function ep(t){var e=t.ctx,o=t.options,n=o.pointLabels,p=Yn(o),b=t.getDistanceFromCenterForValue(o.ticks.reverse?t.min:t.max),M=zt.options._parseFont(n);e.save(),e.font=M.string,e.textBaseline="middle";for(var z=t.chart.data.labels.length-1;z>=0;z--){var r=0===z?p/2:0,c=t.getPointPosition(z,b+r+5),i=Un(n.fontColor,z,Q.global.defaultFontColor);e.fillStyle=i;var a=t.getIndexAngle(z),O=zt.toDegrees(a);e.textAlign=Qn(O),tp(O,t._pointLabelSizes[z],c),Zn(e,t.pointLabels[z],c,M.lineHeight)}e.restore()}function op(t,e,o,n){var p,b=t.ctx,M=e.circular,z=t.chart.data.labels.length,r=Un(e.color,n-1),c=Un(e.lineWidth,n-1);if((M||z)&&r&&c){if(b.save(),b.strokeStyle=r,b.lineWidth=c,b.setLineDash&&(b.setLineDash(e.borderDash||[]),b.lineDashOffset=e.borderDashOffset||0),b.beginPath(),M)b.arc(t.xCenter,t.yCenter,o,0,2*Math.PI);else{p=t.getPointPosition(0,o),b.moveTo(p.x,p.y);for(var i=1;i0&&n>0?o:0)},_drawGrid:function(){var t,e,o,n=this,p=n.ctx,b=n.options,M=b.gridLines,z=b.angleLines,r=Hn(z.lineWidth,M.lineWidth),c=Hn(z.color,M.color);if(b.pointLabels.display&&ep(n),M.display&&zt.each(n.ticks,(function(t,o){0!==o&&(e=n.getDistanceFromCenterForValue(n.ticksAsNumbers[o]),op(n,M,e,o))})),z.display&&r&&c){for(p.save(),p.lineWidth=r,p.strokeStyle=c,p.setLineDash&&(p.setLineDash(Vn([z.borderDash,M.borderDash,[]])),p.lineDashOffset=Vn([z.borderDashOffset,M.borderDashOffset,0])),t=n.chart.data.labels.length-1;t>=0;t--)e=n.getDistanceFromCenterForValue(b.ticks.reverse?n.min:n.max),o=n.getPointPosition(t,e),p.beginPath(),p.moveTo(n.xCenter,n.yCenter),p.lineTo(o.x,o.y),p.stroke();p.restore()}},_drawLabels:function(){var t=this,e=t.ctx,o=t.options.ticks;if(o.display){var n,p,b=t.getIndexAngle(0),M=zt.options._parseFont(o),z=Hn(o.fontColor,Q.global.defaultFontColor);e.save(),e.font=M.string,e.translate(t.xCenter,t.yCenter),e.rotate(b),e.textAlign="center",e.textBaseline="middle",zt.each(t.ticks,(function(b,r){(0!==r||o.reverse)&&(n=t.getDistanceFromCenterForValue(t.ticksAsNumbers[r]),o.showLabelBackdrop&&(p=e.measureText(b).width,e.fillStyle=o.backdropColor,e.fillRect(-p/2-o.backdropPaddingX,-n-M.size/2-o.backdropPaddingY,p+2*o.backdropPaddingX,M.size+2*o.backdropPaddingY)),e.fillStyle=z,e.fillText(b,0,-n))})),e.restore()}},_drawTitle:zt.noop}),bp=$n;pp._defaults=bp;var Mp=zt._deprecated,zp=zt.options.resolve,rp=zt.valueOrDefault,cp=Number.MIN_SAFE_INTEGER||-9007199254740991,ip=Number.MAX_SAFE_INTEGER||9007199254740991,ap={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},Op=Object.keys(ap);function sp(t,e){return t-e}function lp(t){var e,o,n,p={},b=[];for(e=0,o=t.length;ee&&z=0&&M<=z;){if(p=t[(n=M+z>>1)-1]||null,b=t[n],!p)return{lo:null,hi:b};if(b[e]o))return{lo:p,hi:b};z=n-1}}return{lo:b,hi:null}}function qp(t,e,o,n){var p=fp(t,e,o),b=p.lo?p.hi?p.lo:t[t.length-2]:t[0],M=p.lo?p.hi?p.hi:t[t.length-1]:t[1],z=M[e]-b[e],r=z?(o-b[e])/z:0,c=(M[n]-b[n])*r;return b[n]+c}function hp(t,e){var o=t._adapter,n=t.options.time,p=n.parser,b=p||n.format,M=e;return"function"==typeof p&&(M=p(M)),zt.isFinite(M)||(M="string"==typeof b?o.parse(M,b):o.parse(M)),null!==M?+M:(p||"function"!=typeof b||(M=b(e),zt.isFinite(M)||(M=o.parse(M))),M)}function Wp(t,e){if(zt.isNullOrUndef(e))return null;var o=t.options.time,n=hp(t,t.getRightValue(e));return null===n||o.round&&(n=+t._adapter.startOf(n,o.round)),n}function vp(t,e,o,n){var p,b,M,z=Op.length;for(p=Op.indexOf(t);p=Op.indexOf(o);b--)if(M=Op[b],ap[M].common&&t._adapter.diff(p,n,M)>=e-1)return M;return Op[o?Op.indexOf(o):0]}function mp(t){for(var e=Op.indexOf(t)+1,o=Op.length;e1e5*c)throw e+" and "+o+" are too far apart with stepSize of "+c+" "+r;for(p=a;p=0&&(e[b].major=!0);return e}function Lp(t,e,o){var n,p,b=[],M={},z=e.length;for(n=0;n1?lp(l).sort(sp):l.sort(sp),O=Math.min(O,l[0]),s=Math.max(s,l[l.length-1])),O=Wp(z,dp(i))||O,s=Wp(z,up(i))||s,O=O===ip?+c.startOf(Date.now(),a):O,s=s===cp?+c.endOf(Date.now(),a)+1:s,z.min=Math.min(O,s),z.max=Math.max(O+1,s),z._table=[],z._timestamps={data:l,datasets:d,labels:u}},buildTicks:function(){var t,e,o,n=this,p=n.min,b=n.max,M=n.options,z=M.ticks,r=M.time,c=n._timestamps,i=[],a=n.getLabelCapacity(p),O=z.source,s=M.distribution;for(c="data"===O||"auto"===O&&"series"===s?c.data:"labels"===O?c.labels:Rp(n,p,b,a),"ticks"===M.bounds&&c.length&&(p=c[0],b=c[c.length-1]),p=Wp(n,dp(M))||p,b=Wp(n,up(M))||b,t=0,e=c.length;t=p&&o<=b&&i.push(o);return n.min=p,n.max=b,n._unit=r.unit||(z.autoSkip?vp(r.minUnit,n.min,n.max,a):gp(n,i.length,r.minUnit,n.min,n.max)),n._majorUnit=z.major.enabled&&"year"!==n._unit?mp(n._unit):void 0,n._table=Ap(n._timestamps.data,p,b,s),n._offsets=yp(n._table,i,p,b,M),z.reverse&&i.reverse(),Lp(n,i,n._majorUnit)},getLabelForIndex:function(t,e){var o=this,n=o._adapter,p=o.chart.data,b=o.options.time,M=p.labels&&t=0&&t0?z:1}}),Np=Xp;_p._defaults=Np;var wp={category:gn,linear:Cn,logarithmic:In,radialLinear:pp,time:_p},xp={datetime:"MMM D, YYYY, h:mm:ss a",millisecond:"h:mm:ss.SSS a",second:"h:mm:ss a",minute:"h:mm a",hour:"hA",day:"MMM D",week:"ll",month:"MMM YYYY",quarter:"[Q]Q - YYYY",year:"YYYY"};Qo._date.override("function"==typeof t?{_id:"moment",formats:function(){return xp},parse:function(e,o){return"string"==typeof e&&"string"==typeof o?e=t(e,o):e instanceof t||(e=t(e)),e.isValid()?e.valueOf():null},format:function(e,o){return t(e).format(o)},add:function(e,o,n){return t(e).add(o,n).valueOf()},diff:function(e,o,n){return t(e).diff(t(o),n)},startOf:function(e,o,n){return e=t(e),"isoWeek"===o?e.isoWeekday(n).valueOf():e.startOf(o).valueOf()},endOf:function(e,o){return t(e).endOf(o).valueOf()},_create:function(e){return t(e)}}:{}),Q._set("global",{plugins:{filler:{propagate:!0}}});var Tp={dataset:function(t){var e=t.fill,o=t.chart,n=o.getDatasetMeta(e),p=n&&o.isDatasetVisible(e)&&n.dataset._children||[],b=p.length||0;return b?function(t,e){return e=o)&&n;switch(b){case"bottom":return"start";case"top":return"end";case"zero":return"origin";case"origin":case"start":case"end":return b;default:return!1}}function Sp(t){var e,o=t.el._model||{},n=t.el._scale||{},p=t.fill,b=null;if(isFinite(p))return null;if("start"===p?b=void 0===o.scaleBottom?n.bottom:o.scaleBottom:"end"===p?b=void 0===o.scaleTop?n.top:o.scaleTop:void 0!==o.scaleZero?b=o.scaleZero:n.getBasePixel&&(b=n.getBasePixel()),null!=b){if(void 0!==b.x&&void 0!==b.y)return b;if(zt.isFinite(b))return{x:(e=n.isHorizontal())?b:null,y:e?null:b}}return null}function kp(t){var e,o,n,p,b,M=t.el._scale,z=M.options,r=M.chart.data.labels.length,c=t.fill,i=[];if(!r)return null;for(e=z.ticks.reverse?M.max:M.min,o=z.ticks.reverse?M.min:M.max,n=M.getPointPositionForValue(0,e),p=0;p0;--b)zt.canvas.lineTo(t,o[b],o[b-1],!0);else for(M=o[0].cx,z=o[0].cy,r=Math.sqrt(Math.pow(o[0].x-M,2)+Math.pow(o[0].y-z,2)),b=p-1;b>0;--b)t.arc(M,z,r,o[b].angle,o[b-1].angle,!0)}}function Fp(t,e,o,n,p,b){var M,z,r,c,i,a,O,s,l=e.length,d=n.spanGaps,u=[],A=[],f=0,q=0;for(t.beginPath(),M=0,z=l;M=0;--o)(e=r[o].$filler)&&e.visible&&(p=(n=e.el)._view,b=n._children||[],M=e.mapper,z=p.backgroundColor||Q.global.defaultColor,M&&z&&b.length&&(zt.canvas.clipArea(c,t.chartArea),Fp(c,b,M,p,z,n._loop),zt.canvas.unclipArea(c)))}},Up=zt.rtl.getRtlAdapter,Vp=zt.noop,$p=zt.valueOrDefault;function Yp(t,e){return t.usePointStyle&&t.boxWidth>e?e:t.boxWidth}Q._set("global",{legend:{display:!0,position:"top",align:"center",fullWidth:!0,reverse:!1,weight:1e3,onClick:function(t,e){var o=e.datasetIndex,n=this.chart,p=n.getDatasetMeta(o);p.hidden=null===p.hidden?!n.data.datasets[o].hidden:null,n.update()},onHover:null,onLeave:null,labels:{boxWidth:40,padding:10,generateLabels:function(t){var e=t.data.datasets,o=t.options.legend||{},n=o.labels&&o.labels.usePointStyle;return t._getSortedDatasetMetas().map((function(o){var p=o.controller.getStyle(n?0:void 0);return{text:e[o.index].label,fillStyle:p.backgroundColor,hidden:!t.isDatasetVisible(o.index),lineCap:p.borderCapStyle,lineDash:p.borderDash,lineDashOffset:p.borderDashOffset,lineJoin:p.borderJoinStyle,lineWidth:p.borderWidth,strokeStyle:p.borderColor,pointStyle:p.pointStyle,rotation:p.rotation,datasetIndex:o.index}}),this)}}},legendCallback:function(t){var e,o,n,p=document.createElement("ul"),b=t.data.datasets;for(p.setAttribute("class",t.id+"-legend"),e=0,o=b.length;er.width)&&(a+=M+o.padding,i[i.length-(e>0?0:1)]=0),z[e]={left:0,top:0,width:n,height:M},i[i.length-1]+=n+o.padding})),r.height+=a}else{var O=o.padding,s=t.columnWidths=[],l=t.columnHeights=[],d=o.padding,u=0,A=0;zt.each(t.legendItems,(function(t,e){var n=Yp(o,M)+M/2+p.measureText(t.text).width;e>0&&A+M+2*O>r.height&&(d+=u+o.padding,s.push(u),l.push(A),u=0,A=0),u=Math.max(u,n),A+=M+O,z[e]={left:0,top:0,width:n,height:M}})),d+=u,s.push(u),l.push(A),r.width+=d}t.width=r.width,t.height=r.height}else t.width=r.width=t.height=r.height=0},afterFit:Vp,isHorizontal:function(){return"top"===this.options.position||"bottom"===this.options.position},draw:function(){var t=this,e=t.options,o=e.labels,n=Q.global,p=n.defaultColor,b=n.elements.line,M=t.height,z=t.columnHeights,r=t.width,c=t.lineWidths;if(e.display){var i,a=Up(e.rtl,t.left,t.minSize.width),O=t.ctx,s=$p(o.fontColor,n.defaultFontColor),l=zt.options._parseFont(o),d=l.size;O.textAlign=a.textAlign("left"),O.textBaseline="middle",O.lineWidth=.5,O.strokeStyle=s,O.fillStyle=s,O.font=l.string;var u=Yp(o,d),A=t.legendHitBoxes,f=function(t,e,n){if(!(isNaN(u)||u<=0)){O.save();var M=$p(n.lineWidth,b.borderWidth);if(O.fillStyle=$p(n.fillStyle,p),O.lineCap=$p(n.lineCap,b.borderCapStyle),O.lineDashOffset=$p(n.lineDashOffset,b.borderDashOffset),O.lineJoin=$p(n.lineJoin,b.borderJoinStyle),O.lineWidth=M,O.strokeStyle=$p(n.strokeStyle,p),O.setLineDash&&O.setLineDash($p(n.lineDash,b.borderDash)),o&&o.usePointStyle){var z=u*Math.SQRT2/2,r=a.xPlus(t,u/2),c=e+d/2;zt.canvas.drawPoint(O,n.pointStyle,z,r,c,n.rotation)}else O.fillRect(a.leftForLtr(t,u),e,u,d),0!==M&&O.strokeRect(a.leftForLtr(t,u),e,u,d);O.restore()}},q=function(t,e,o,n){var p=d/2,b=a.xPlus(t,u+p),M=e+p;O.fillText(o.text,b,M),o.hidden&&(O.beginPath(),O.lineWidth=2,O.moveTo(b,M),O.lineTo(a.xPlus(b,n),M),O.stroke())},h=function(t,n){switch(e.align){case"start":return o.padding;case"end":return t-n;default:return(t-n+o.padding)/2}},W=t.isHorizontal();i=W?{x:t.left+h(r,c[0]),y:t.top+o.padding,line:0}:{x:t.left+o.padding,y:t.top+h(M,z[0]),line:0},zt.rtl.overrideTextDirection(t.ctx,e.textDirection);var v=d+o.padding;zt.each(t.legendItems,(function(e,n){var p=O.measureText(e.text).width,b=u+d/2+p,s=i.x,l=i.y;a.setWidth(t.minSize.width),W?n>0&&s+b+o.padding>t.left+t.minSize.width&&(l=i.y+=v,i.line++,s=i.x=t.left+h(r,c[i.line])):n>0&&l+v>t.top+t.minSize.height&&(s=i.x=s+t.columnWidths[i.line]+o.padding,i.line++,l=i.y=t.top+h(M,z[i.line]));var g=a.x(s);f(g,l,e),A[n].left=a.leftForLtr(g,A[n].width),A[n].top=l,q(g,l,e,p),W?i.x+=b+o.padding:i.y+=v})),zt.rtl.restoreTextDirection(t.ctx,e.textDirection)}},_getLegendItemAt:function(t,e){var o,n,p,b=this;if(t>=b.left&&t<=b.right&&e>=b.top&&e<=b.bottom)for(p=b.legendHitBoxes,o=0;o=(n=p[o]).left&&t<=n.left+n.width&&e>=n.top&&e<=n.top+n.height)return b.legendItems[o];return null},handleEvent:function(t){var e,o=this,n=o.options,p="mouseup"===t.type?"click":t.type;if("mousemove"===p){if(!n.onHover&&!n.onLeave)return}else{if("click"!==p)return;if(!n.onClick)return}e=o._getLegendItemAt(t.x,t.y),"click"===p?e&&n.onClick&&n.onClick.call(o,t.native,e):(n.onLeave&&e!==o._hoveredItem&&(o._hoveredItem&&n.onLeave.call(o,t.native,o._hoveredItem),o._hoveredItem=e),n.onHover&&e&&n.onHover.call(o,t.native,e))}});function Jp(t,e){var o=new Gp({ctx:t.ctx,options:e,chart:t});Ue.configure(t,o,e),Ue.addBox(t,o),t.legend=o}var Kp={id:"legend",_element:Gp,beforeInit:function(t){var e=t.options.legend;e&&Jp(t,e)},beforeUpdate:function(t){var e=t.options.legend,o=t.legend;e?(zt.mergeIf(e,Q.global.legend),o?(Ue.configure(t,o,e),o.options=e):Jp(t,e)):o&&(Ue.removeBox(t,o),delete t.legend)},afterEvent:function(t,e){var o=t.legend;o&&o.handleEvent(e)}},Qp=zt.noop;Q._set("global",{title:{display:!1,fontStyle:"bold",fullWidth:!0,padding:10,position:"top",text:"",weight:2e3}});var Zp=dt.extend({initialize:function(t){var e=this;zt.extend(e,t),e.legendHitBoxes=[]},beforeUpdate:Qp,update:function(t,e,o){var n=this;return n.beforeUpdate(),n.maxWidth=t,n.maxHeight=e,n.margins=o,n.beforeSetDimensions(),n.setDimensions(),n.afterSetDimensions(),n.beforeBuildLabels(),n.buildLabels(),n.afterBuildLabels(),n.beforeFit(),n.fit(),n.afterFit(),n.afterUpdate(),n.minSize},afterUpdate:Qp,beforeSetDimensions:Qp,setDimensions:function(){var t=this;t.isHorizontal()?(t.width=t.maxWidth,t.left=0,t.right=t.width):(t.height=t.maxHeight,t.top=0,t.bottom=t.height),t.paddingLeft=0,t.paddingTop=0,t.paddingRight=0,t.paddingBottom=0,t.minSize={width:0,height:0}},afterSetDimensions:Qp,beforeBuildLabels:Qp,buildLabels:Qp,afterBuildLabels:Qp,beforeFit:Qp,fit:function(){var t,e=this,o=e.options,n=e.minSize={},p=e.isHorizontal();o.display?(t=(zt.isArray(o.text)?o.text.length:1)*zt.options._parseFont(o).lineHeight+2*o.padding,e.width=n.width=p?e.maxWidth:t,e.height=n.height=p?t:e.maxHeight):e.width=n.width=e.height=n.height=0},afterFit:Qp,isHorizontal:function(){var t=this.options.position;return"top"===t||"bottom"===t},draw:function(){var t=this,e=t.ctx,o=t.options;if(o.display){var n,p,b,M=zt.options._parseFont(o),z=M.lineHeight,r=z/2+o.padding,c=0,i=t.top,a=t.left,O=t.bottom,s=t.right;e.fillStyle=zt.valueOrDefault(o.fontColor,Q.global.defaultFontColor),e.font=M.string,t.isHorizontal()?(p=a+(s-a)/2,b=i+r,n=s-a):(p="left"===o.position?a+r:s-r,b=i+(O-i)/2,n=O-i,c=Math.PI*("left"===o.position?-.5:.5)),e.save(),e.translate(p,b),e.rotate(c),e.textAlign="center",e.textBaseline="middle";var l=o.text;if(zt.isArray(l))for(var d=0,u=0;u0&&e-1 in t)}m.fn=m.prototype={jquery:g,constructor:m,length:0,toArray:function(){return z.call(this)},get:function(t){return null==t?z.call(this):t<0?this[t+this.length]:this[t]},pushStack:function(t){var e=m.merge(this.constructor(),t);return e.prevObject=this,e},each:function(t){return m.each(this,t)},map:function(t){return this.pushStack(m.map(this,(function(e,o){return t.call(e,o,e)})))},slice:function(){return this.pushStack(z.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},even:function(){return this.pushStack(m.grep(this,(function(t,e){return(e+1)%2})))},odd:function(){return this.pushStack(m.grep(this,(function(t,e){return e%2})))},eq:function(t){var e=this.length,o=+t+(t<0?e:0);return this.pushStack(o>=0&&o+~]|[\\x20\\t\\r\\n\\f])[\\x20\\t\\r\\n\\f]*"),U=new RegExp(k+"|>"),V=new RegExp(P),$=new RegExp("^"+E+"$"),Y={ID:new RegExp("^#("+E+")"),CLASS:new RegExp("^\\.("+E+")"),TAG:new RegExp("^("+E+"|[*])"),ATTR:new RegExp("^"+D),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\([\\x20\\t\\r\\n\\f]*(even|odd|(([+-]|)(\\d*)n|)[\\x20\\t\\r\\n\\f]*(?:([+-]|)[\\x20\\t\\r\\n\\f]*(\\d+)|))[\\x20\\t\\r\\n\\f]*\\)|)","i"),bool:new RegExp("^(?:"+S+")$","i"),needsContext:new RegExp("^[\\x20\\t\\r\\n\\f]*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\([\\x20\\t\\r\\n\\f]*((?:-\\d)?\\d*)[\\x20\\t\\r\\n\\f]*\\)|)(?=[^-]|$)","i")},G=/HTML$/i,J=/^(?:input|select|textarea|button)$/i,K=/^h\d$/i,Q=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,tt=/[+~]/,et=new RegExp("\\\\[\\da-fA-F]{1,6}[\\x20\\t\\r\\n\\f]?|\\\\([^\\r\\n\\f])","g"),ot=function(t,e){var o="0x"+t.slice(1)-65536;return e||(o<0?String.fromCharCode(o+65536):String.fromCharCode(o>>10|55296,1023&o|56320))},nt=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,pt=function(t,e){return e?"\0"===t?"�":t.slice(0,-1)+"\\"+t.charCodeAt(t.length-1).toString(16)+" ":"\\"+t},bt=function(){O()},Mt=ht((function(t){return!0===t.disabled&&"fieldset"===t.nodeName.toLowerCase()}),{dir:"parentNode",next:"legend"});try{x.apply(_=T.call(W.childNodes),W.childNodes),_[W.childNodes.length].nodeType}catch(t){x={apply:_.length?function(t,e){w.apply(t,T.call(e))}:function(t,e){for(var o=t.length,n=0;t[o++]=e[n++];);t.length=o-1}}}function zt(t,e,n,p){var b,z,c,i,a,l,A,f=e&&e.ownerDocument,W=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==W&&9!==W&&11!==W)return n;if(!p&&(O(e),e=e||s,d)){if(11!==W&&(a=Z.exec(t)))if(b=a[1]){if(9===W){if(!(c=e.getElementById(b)))return n;if(c.id===b)return n.push(c),n}else if(f&&(c=f.getElementById(b))&&q(e,c)&&c.id===b)return n.push(c),n}else{if(a[2])return x.apply(n,e.getElementsByTagName(t)),n;if((b=a[3])&&o.getElementsByClassName&&e.getElementsByClassName)return x.apply(n,e.getElementsByClassName(b)),n}if(o.qsa&&!B[t+" "]&&(!u||!u.test(t))&&(1!==W||"object"!==e.nodeName.toLowerCase())){if(A=t,f=e,1===W&&(U.test(t)||H.test(t))){for((f=tt.test(t)&&At(e.parentNode)||e)===e&&o.scope||((i=e.getAttribute("id"))?i=i.replace(nt,pt):e.setAttribute("id",i=h)),z=(l=M(t)).length;z--;)l[z]=(i?"#"+i:":scope")+" "+qt(l[z]);A=l.join(",")}try{return x.apply(n,f.querySelectorAll(A)),n}catch(e){B(t,!0)}finally{i===h&&e.removeAttribute("id")}}}return r(t.replace(I,"$1"),e,n,p)}function rt(){var t=[];return function e(o,p){return t.push(o+" ")>n.cacheLength&&delete e[t.shift()],e[o+" "]=p}}function ct(t){return t[h]=!0,t}function it(t){var e=s.createElement("fieldset");try{return!!t(e)}catch(t){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function at(t,e){for(var o=t.split("|"),p=o.length;p--;)n.attrHandle[o[p]]=e}function Ot(t,e){var o=e&&t,n=o&&1===t.nodeType&&1===e.nodeType&&t.sourceIndex-e.sourceIndex;if(n)return n;if(o)for(;o=o.nextSibling;)if(o===e)return-1;return t?1:-1}function st(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function lt(t){return function(e){var o=e.nodeName.toLowerCase();return("input"===o||"button"===o)&&e.type===t}}function dt(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&Mt(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ut(t){return ct((function(e){return e=+e,ct((function(o,n){for(var p,b=t([],o.length,e),M=b.length;M--;)o[p=b[M]]&&(o[p]=!(n[p]=o[p]))}))}))}function At(t){return t&&void 0!==t.getElementsByTagName&&t}for(e in o=zt.support={},b=zt.isXML=function(t){var e=t&&t.namespaceURI,o=t&&(t.ownerDocument||t).documentElement;return!G.test(e||o&&o.nodeName||"HTML")},O=zt.setDocument=function(t){var e,p,M=t?t.ownerDocument||t:W;return M!=s&&9===M.nodeType&&M.documentElement?(l=(s=M).documentElement,d=!b(s),W!=s&&(p=s.defaultView)&&p.top!==p&&(p.addEventListener?p.addEventListener("unload",bt,!1):p.attachEvent&&p.attachEvent("onunload",bt)),o.scope=it((function(t){return l.appendChild(t).appendChild(s.createElement("div")),void 0!==t.querySelectorAll&&!t.querySelectorAll(":scope fieldset div").length})),o.attributes=it((function(t){return t.className="i",!t.getAttribute("className")})),o.getElementsByTagName=it((function(t){return t.appendChild(s.createComment("")),!t.getElementsByTagName("*").length})),o.getElementsByClassName=Q.test(s.getElementsByClassName),o.getById=it((function(t){return l.appendChild(t).id=h,!s.getElementsByName||!s.getElementsByName(h).length})),o.getById?(n.filter.ID=function(t){var e=t.replace(et,ot);return function(t){return t.getAttribute("id")===e}},n.find.ID=function(t,e){if(void 0!==e.getElementById&&d){var o=e.getElementById(t);return o?[o]:[]}}):(n.filter.ID=function(t){var e=t.replace(et,ot);return function(t){var o=void 0!==t.getAttributeNode&&t.getAttributeNode("id");return o&&o.value===e}},n.find.ID=function(t,e){if(void 0!==e.getElementById&&d){var o,n,p,b=e.getElementById(t);if(b){if((o=b.getAttributeNode("id"))&&o.value===t)return[b];for(p=e.getElementsByName(t),n=0;b=p[n++];)if((o=b.getAttributeNode("id"))&&o.value===t)return[b]}return[]}}),n.find.TAG=o.getElementsByTagName?function(t,e){return void 0!==e.getElementsByTagName?e.getElementsByTagName(t):o.qsa?e.querySelectorAll(t):void 0}:function(t,e){var o,n=[],p=0,b=e.getElementsByTagName(t);if("*"===t){for(;o=b[p++];)1===o.nodeType&&n.push(o);return n}return b},n.find.CLASS=o.getElementsByClassName&&function(t,e){if(void 0!==e.getElementsByClassName&&d)return e.getElementsByClassName(t)},A=[],u=[],(o.qsa=Q.test(s.querySelectorAll))&&(it((function(t){var e;l.appendChild(t).innerHTML="",t.querySelectorAll("[msallowcapture^='']").length&&u.push("[*^$]=[\\x20\\t\\r\\n\\f]*(?:''|\"\")"),t.querySelectorAll("[selected]").length||u.push("\\[[\\x20\\t\\r\\n\\f]*(?:value|"+S+")"),t.querySelectorAll("[id~="+h+"-]").length||u.push("~="),(e=s.createElement("input")).setAttribute("name",""),t.appendChild(e),t.querySelectorAll("[name='']").length||u.push("\\[[\\x20\\t\\r\\n\\f]*name[\\x20\\t\\r\\n\\f]*=[\\x20\\t\\r\\n\\f]*(?:''|\"\")"),t.querySelectorAll(":checked").length||u.push(":checked"),t.querySelectorAll("a#"+h+"+*").length||u.push(".#.+[+~]"),t.querySelectorAll("\\\f"),u.push("[\\r\\n\\f]")})),it((function(t){t.innerHTML="";var e=s.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&u.push("name[\\x20\\t\\r\\n\\f]*[*^$|!~]?="),2!==t.querySelectorAll(":enabled").length&&u.push(":enabled",":disabled"),l.appendChild(t).disabled=!0,2!==t.querySelectorAll(":disabled").length&&u.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),u.push(",.*:")}))),(o.matchesSelector=Q.test(f=l.matches||l.webkitMatchesSelector||l.mozMatchesSelector||l.oMatchesSelector||l.msMatchesSelector))&&it((function(t){o.disconnectedMatch=f.call(t,"*"),f.call(t,"[s!='']:x"),A.push("!=",P)})),u=u.length&&new RegExp(u.join("|")),A=A.length&&new RegExp(A.join("|")),e=Q.test(l.compareDocumentPosition),q=e||Q.test(l.contains)?function(t,e){var o=9===t.nodeType?t.documentElement:t,n=e&&e.parentNode;return t===n||!(!n||1!==n.nodeType||!(o.contains?o.contains(n):t.compareDocumentPosition&&16&t.compareDocumentPosition(n)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},L=e?function(t,e){if(t===e)return a=!0,0;var n=!t.compareDocumentPosition-!e.compareDocumentPosition;return n||(1&(n=(t.ownerDocument||t)==(e.ownerDocument||e)?t.compareDocumentPosition(e):1)||!o.sortDetached&&e.compareDocumentPosition(t)===n?t==s||t.ownerDocument==W&&q(W,t)?-1:e==s||e.ownerDocument==W&&q(W,e)?1:i?C(i,t)-C(i,e):0:4&n?-1:1)}:function(t,e){if(t===e)return a=!0,0;var o,n=0,p=t.parentNode,b=e.parentNode,M=[t],z=[e];if(!p||!b)return t==s?-1:e==s?1:p?-1:b?1:i?C(i,t)-C(i,e):0;if(p===b)return Ot(t,e);for(o=t;o=o.parentNode;)M.unshift(o);for(o=e;o=o.parentNode;)z.unshift(o);for(;M[n]===z[n];)n++;return n?Ot(M[n],z[n]):M[n]==W?-1:z[n]==W?1:0},s):s},zt.matches=function(t,e){return zt(t,null,null,e)},zt.matchesSelector=function(t,e){if(O(t),o.matchesSelector&&d&&!B[e+" "]&&(!A||!A.test(e))&&(!u||!u.test(e)))try{var n=f.call(t,e);if(n||o.disconnectedMatch||t.document&&11!==t.document.nodeType)return n}catch(t){B(e,!0)}return zt(e,s,null,[t]).length>0},zt.contains=function(t,e){return(t.ownerDocument||t)!=s&&O(t),q(t,e)},zt.attr=function(t,e){(t.ownerDocument||t)!=s&&O(t);var p=n.attrHandle[e.toLowerCase()],b=p&&X.call(n.attrHandle,e.toLowerCase())?p(t,e,!d):void 0;return void 0!==b?b:o.attributes||!d?t.getAttribute(e):(b=t.getAttributeNode(e))&&b.specified?b.value:null},zt.escape=function(t){return(t+"").replace(nt,pt)},zt.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},zt.uniqueSort=function(t){var e,n=[],p=0,b=0;if(a=!o.detectDuplicates,i=!o.sortStable&&t.slice(0),t.sort(L),a){for(;e=t[b++];)e===t[b]&&(p=n.push(b));for(;p--;)t.splice(n[p],1)}return i=null,t},p=zt.getText=function(t){var e,o="",n=0,b=t.nodeType;if(b){if(1===b||9===b||11===b){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)o+=p(t)}else if(3===b||4===b)return t.nodeValue}else for(;e=t[n++];)o+=p(e);return o},n=zt.selectors={cacheLength:50,createPseudo:ct,match:Y,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(et,ot),t[3]=(t[3]||t[4]||t[5]||"").replace(et,ot),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||zt.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&zt.error(t[0]),t},PSEUDO:function(t){var e,o=!t[6]&&t[2];return Y.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":o&&V.test(o)&&(e=M(o,!0))&&(e=o.indexOf(")",o.length-e)-o.length)&&(t[0]=t[0].slice(0,e),t[2]=o.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(et,ot).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=m[t+" "];return e||(e=new RegExp("(^|[\\x20\\t\\r\\n\\f])"+t+"("+k+"|$)"))&&m(t,(function(t){return e.test("string"==typeof t.className&&t.className||void 0!==t.getAttribute&&t.getAttribute("class")||"")}))},ATTR:function(t,e,o){return function(n){var p=zt.attr(n,t);return null==p?"!="===e:!e||(p+="","="===e?p===o:"!="===e?p!==o:"^="===e?o&&0===p.indexOf(o):"*="===e?o&&p.indexOf(o)>-1:"$="===e?o&&p.slice(-o.length)===o:"~="===e?(" "+p.replace(j," ")+" ").indexOf(o)>-1:"|="===e&&(p===o||p.slice(0,o.length+1)===o+"-"))}},CHILD:function(t,e,o,n,p){var b="nth"!==t.slice(0,3),M="last"!==t.slice(-4),z="of-type"===e;return 1===n&&0===p?function(t){return!!t.parentNode}:function(e,o,r){var c,i,a,O,s,l,d=b!==M?"nextSibling":"previousSibling",u=e.parentNode,A=z&&e.nodeName.toLowerCase(),f=!r&&!z,q=!1;if(u){if(b){for(;d;){for(O=e;O=O[d];)if(z?O.nodeName.toLowerCase()===A:1===O.nodeType)return!1;l=d="only"===t&&!l&&"nextSibling"}return!0}if(l=[M?u.firstChild:u.lastChild],M&&f){for(q=(s=(c=(i=(a=(O=u)[h]||(O[h]={}))[O.uniqueID]||(a[O.uniqueID]={}))[t]||[])[0]===v&&c[1])&&c[2],O=s&&u.childNodes[s];O=++s&&O&&O[d]||(q=s=0)||l.pop();)if(1===O.nodeType&&++q&&O===e){i[t]=[v,s,q];break}}else if(f&&(q=s=(c=(i=(a=(O=e)[h]||(O[h]={}))[O.uniqueID]||(a[O.uniqueID]={}))[t]||[])[0]===v&&c[1]),!1===q)for(;(O=++s&&O&&O[d]||(q=s=0)||l.pop())&&((z?O.nodeName.toLowerCase()!==A:1!==O.nodeType)||!++q||(f&&((i=(a=O[h]||(O[h]={}))[O.uniqueID]||(a[O.uniqueID]={}))[t]=[v,q]),O!==e)););return(q-=p)===n||q%n==0&&q/n>=0}}},PSEUDO:function(t,e){var o,p=n.pseudos[t]||n.setFilters[t.toLowerCase()]||zt.error("unsupported pseudo: "+t);return p[h]?p(e):p.length>1?(o=[t,t,"",e],n.setFilters.hasOwnProperty(t.toLowerCase())?ct((function(t,o){for(var n,b=p(t,e),M=b.length;M--;)t[n=C(t,b[M])]=!(o[n]=b[M])})):function(t){return p(t,0,o)}):p}},pseudos:{not:ct((function(t){var e=[],o=[],n=z(t.replace(I,"$1"));return n[h]?ct((function(t,e,o,p){for(var b,M=n(t,null,p,[]),z=t.length;z--;)(b=M[z])&&(t[z]=!(e[z]=b))})):function(t,p,b){return e[0]=t,n(e,null,b,o),e[0]=null,!o.pop()}})),has:ct((function(t){return function(e){return zt(t,e).length>0}})),contains:ct((function(t){return t=t.replace(et,ot),function(e){return(e.textContent||p(e)).indexOf(t)>-1}})),lang:ct((function(t){return $.test(t||"")||zt.error("unsupported lang: "+t),t=t.replace(et,ot).toLowerCase(),function(e){var o;do{if(o=d?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(o=o.toLowerCase())===t||0===o.indexOf(t+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}})),target:function(e){var o=t.location&&t.location.hash;return o&&o.slice(1)===e.id},root:function(t){return t===l},focus:function(t){return t===s.activeElement&&(!s.hasFocus||s.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:dt(!1),disabled:dt(!0),checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,!0===t.selected},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!n.pseudos.empty(t)},header:function(t){return K.test(t.nodeName)},input:function(t){return J.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:ut((function(){return[0]})),last:ut((function(t,e){return[e-1]})),eq:ut((function(t,e,o){return[o<0?o+e:o]})),even:ut((function(t,e){for(var o=0;oe?e:o;--n>=0;)t.push(n);return t})),gt:ut((function(t,e,o){for(var n=o<0?o+e:o;++n1?function(e,o,n){for(var p=t.length;p--;)if(!t[p](e,o,n))return!1;return!0}:t[0]}function vt(t,e,o,n,p){for(var b,M=[],z=0,r=t.length,c=null!=e;z-1&&(b[c]=!(M[c]=a))}}else A=vt(A===M?A.splice(l,A.length):A),p?p(null,M,A,r):x.apply(M,A)}))}function mt(t){for(var e,o,p,b=t.length,M=n.relative[t[0].type],z=M||n.relative[" "],r=M?1:0,i=ht((function(t){return t===e}),z,!0),a=ht((function(t){return C(e,t)>-1}),z,!0),O=[function(t,o,n){var p=!M&&(n||o!==c)||((e=o).nodeType?i(t,o,n):a(t,o,n));return e=null,p}];r1&&Wt(O),r>1&&qt(t.slice(0,r-1).concat({value:" "===t[r-2].type?"*":""})).replace(I,"$1"),o,r0,p=t.length>0,b=function(b,M,z,r,i){var a,l,u,A=0,f="0",q=b&&[],h=[],W=c,g=b||p&&n.find.TAG("*",i),m=v+=null==W?1:Math.random()||.1,R=g.length;for(i&&(c=M==s||M||i);f!==R&&null!=(a=g[f]);f++){if(p&&a){for(l=0,M||a.ownerDocument==s||(O(a),z=!d);u=t[l++];)if(u(a,M||s,z)){r.push(a);break}i&&(v=m)}o&&((a=!u&&a)&&A--,b&&q.push(a))}if(A+=f,o&&f!==A){for(l=0;u=e[l++];)u(q,h,M,z);if(b){if(A>0)for(;f--;)q[f]||h[f]||(h[f]=N.call(r));h=vt(h)}x.apply(r,h),i&&!b&&h.length>0&&A+e.length>1&&zt.uniqueSort(r)}return i&&(v=m,c=W),q};return o?ct(b):b}(b,p)),z.selector=t}return z},r=zt.select=function(t,e,o,p){var b,r,c,i,a,O="function"==typeof t&&t,s=!p&&M(t=O.selector||t);if(o=o||[],1===s.length){if((r=s[0]=s[0].slice(0)).length>2&&"ID"===(c=r[0]).type&&9===e.nodeType&&d&&n.relative[r[1].type]){if(!(e=(n.find.ID(c.matches[0].replace(et,ot),e)||[])[0]))return o;O&&(e=e.parentNode),t=t.slice(r.shift().value.length)}for(b=Y.needsContext.test(t)?0:r.length;b--&&(c=r[b],!n.relative[i=c.type]);)if((a=n.find[i])&&(p=a(c.matches[0].replace(et,ot),tt.test(r[0].type)&&At(e.parentNode)||e))){if(r.splice(b,1),!(t=p.length&&qt(r)))return x.apply(o,p),o;break}}return(O||z(t,s))(p,e,!d,o,!e||tt.test(t)&&At(e.parentNode)||e),o},o.sortStable=h.split("").sort(L).join("")===h,o.detectDuplicates=!!a,O(),o.sortDetached=it((function(t){return 1&t.compareDocumentPosition(s.createElement("fieldset"))})),it((function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")}))||at("type|href|height|width",(function(t,e,o){if(!o)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)})),o.attributes&&it((function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")}))||at("value",(function(t,e,o){if(!o&&"input"===t.nodeName.toLowerCase())return t.defaultValue})),it((function(t){return null==t.getAttribute("disabled")}))||at(S,(function(t,e,o){var n;if(!o)return!0===t[e]?e.toLowerCase():(n=t.getAttributeNode(e))&&n.specified?n.value:null})),zt}(n);m.find=y,m.expr=y.selectors,m.expr[":"]=m.expr.pseudos,m.uniqueSort=m.unique=y.uniqueSort,m.text=y.getText,m.isXMLDoc=y.isXML,m.contains=y.contains,m.escapeSelector=y.escape;var B=function(t,e,o){for(var n=[],p=void 0!==o;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(p&&m(t).is(o))break;n.push(t)}return n},L=function(t,e){for(var o=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&o.push(t);return o},X=m.expr.match.needsContext;function _(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()}var N=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function w(t,e,o){return A(e)?m.grep(t,(function(t,n){return!!e.call(t,n,t)!==o})):e.nodeType?m.grep(t,(function(t){return t===e!==o})):"string"!=typeof e?m.grep(t,(function(t){return i.call(e,t)>-1!==o})):m.filter(e,t,o)}m.filter=function(t,e,o){var n=e[0];return o&&(t=":not("+t+")"),1===e.length&&1===n.nodeType?m.find.matchesSelector(n,t)?[n]:[]:m.find.matches(t,m.grep(e,(function(t){return 1===t.nodeType})))},m.fn.extend({find:function(t){var e,o,n=this.length,p=this;if("string"!=typeof t)return this.pushStack(m(t).filter((function(){for(e=0;e1?m.uniqueSort(o):o},filter:function(t){return this.pushStack(w(this,t||[],!1))},not:function(t){return this.pushStack(w(this,t||[],!0))},is:function(t){return!!w(this,"string"==typeof t&&X.test(t)?m(t):t||[],!1).length}});var x,T=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(m.fn.init=function(t,e,o){var n,p;if(!t)return this;if(o=o||x,"string"==typeof t){if(!(n="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:T.exec(t))||!n[1]&&e)return!e||e.jquery?(e||o).find(t):this.constructor(e).find(t);if(n[1]){if(e=e instanceof m?e[0]:e,m.merge(this,m.parseHTML(n[1],e&&e.nodeType?e.ownerDocument||e:q,!0)),N.test(n[1])&&m.isPlainObject(e))for(n in e)A(this[n])?this[n](e[n]):this.attr(n,e[n]);return this}return(p=q.getElementById(n[2]))&&(this[0]=p,this.length=1),this}return t.nodeType?(this[0]=t,this.length=1,this):A(t)?void 0!==o.ready?o.ready(t):t(m):m.makeArray(t,this)}).prototype=m.fn,x=m(q);var C=/^(?:parents|prev(?:Until|All))/,S={children:!0,contents:!0,next:!0,prev:!0};function k(t,e){for(;(t=t[e])&&1!==t.nodeType;);return t}m.fn.extend({has:function(t){var e=m(t,this),o=e.length;return this.filter((function(){for(var t=0;t-1:1===o.nodeType&&m.find.matchesSelector(o,t))){b.push(o);break}return this.pushStack(b.length>1?m.uniqueSort(b):b)},index:function(t){return t?"string"==typeof t?i.call(m(t),this[0]):i.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(m.uniqueSort(m.merge(this.get(),m(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),m.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return B(t,"parentNode")},parentsUntil:function(t,e,o){return B(t,"parentNode",o)},next:function(t){return k(t,"nextSibling")},prev:function(t){return k(t,"previousSibling")},nextAll:function(t){return B(t,"nextSibling")},prevAll:function(t){return B(t,"previousSibling")},nextUntil:function(t,e,o){return B(t,"nextSibling",o)},prevUntil:function(t,e,o){return B(t,"previousSibling",o)},siblings:function(t){return L((t.parentNode||{}).firstChild,t)},children:function(t){return L(t.firstChild)},contents:function(t){return null!=t.contentDocument&&M(t.contentDocument)?t.contentDocument:(_(t,"template")&&(t=t.content||t),m.merge([],t.childNodes))}},(function(t,e){m.fn[t]=function(o,n){var p=m.map(this,e,o);return"Until"!==t.slice(-5)&&(n=o),n&&"string"==typeof n&&(p=m.filter(n,p)),this.length>1&&(S[t]||m.uniqueSort(p),C.test(t)&&p.reverse()),this.pushStack(p)}}));var E=/[^\x20\t\r\n\f]+/g;function D(t){return t}function P(t){throw t}function j(t,e,o,n){var p;try{t&&A(p=t.promise)?p.call(t).done(e).fail(o):t&&A(p=t.then)?p.call(t,e,o):e.apply(void 0,[t].slice(n))}catch(t){o.apply(void 0,[t])}}m.Callbacks=function(t){t="string"==typeof t?function(t){var e={};return m.each(t.match(E)||[],(function(t,o){e[o]=!0})),e}(t):m.extend({},t);var e,o,n,p,b=[],M=[],z=-1,r=function(){for(p=p||t.once,n=e=!0;M.length;z=-1)for(o=M.shift();++z-1;)b.splice(o,1),o<=z&&z--})),this},has:function(t){return t?m.inArray(t,b)>-1:b.length>0},empty:function(){return b&&(b=[]),this},disable:function(){return p=M=[],b=o="",this},disabled:function(){return!b},lock:function(){return p=M=[],o||e||(b=o=""),this},locked:function(){return!!p},fireWith:function(t,o){return p||(o=[t,(o=o||[]).slice?o.slice():o],M.push(o),e||r()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!n}};return c},m.extend({Deferred:function(t){var e=[["notify","progress",m.Callbacks("memory"),m.Callbacks("memory"),2],["resolve","done",m.Callbacks("once memory"),m.Callbacks("once memory"),0,"resolved"],["reject","fail",m.Callbacks("once memory"),m.Callbacks("once memory"),1,"rejected"]],o="pending",p={state:function(){return o},always:function(){return b.done(arguments).fail(arguments),this},catch:function(t){return p.then(null,t)},pipe:function(){var t=arguments;return m.Deferred((function(o){m.each(e,(function(e,n){var p=A(t[n[4]])&&t[n[4]];b[n[1]]((function(){var t=p&&p.apply(this,arguments);t&&A(t.promise)?t.promise().progress(o.notify).done(o.resolve).fail(o.reject):o[n[0]+"With"](this,p?[t]:arguments)}))})),t=null})).promise()},then:function(t,o,p){var b=0;function M(t,e,o,p){return function(){var z=this,r=arguments,c=function(){var n,c;if(!(t=b&&(o!==P&&(z=void 0,r=[n]),e.rejectWith(z,r))}};t?i():(m.Deferred.getStackHook&&(i.stackTrace=m.Deferred.getStackHook()),n.setTimeout(i))}}return m.Deferred((function(n){e[0][3].add(M(0,n,A(p)?p:D,n.notifyWith)),e[1][3].add(M(0,n,A(t)?t:D)),e[2][3].add(M(0,n,A(o)?o:P))})).promise()},promise:function(t){return null!=t?m.extend(t,p):p}},b={};return m.each(e,(function(t,n){var M=n[2],z=n[5];p[n[1]]=M.add,z&&M.add((function(){o=z}),e[3-t][2].disable,e[3-t][3].disable,e[0][2].lock,e[0][3].lock),M.add(n[3].fire),b[n[0]]=function(){return b[n[0]+"With"](this===b?void 0:this,arguments),this},b[n[0]+"With"]=M.fireWith})),p.promise(b),t&&t.call(b,b),b},when:function(t){var e=arguments.length,o=e,n=Array(o),p=z.call(arguments),b=m.Deferred(),M=function(t){return function(o){n[t]=this,p[t]=arguments.length>1?z.call(arguments):o,--e||b.resolveWith(n,p)}};if(e<=1&&(j(t,b.done(M(o)).resolve,b.reject,!e),"pending"===b.state()||A(p[o]&&p[o].then)))return b.then();for(;o--;)j(p[o],M(o),b.reject);return b.promise()}});var I=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;m.Deferred.exceptionHook=function(t,e){n.console&&n.console.warn&&t&&I.test(t.name)&&n.console.warn("jQuery.Deferred exception: "+t.message,t.stack,e)},m.readyException=function(t){n.setTimeout((function(){throw t}))};var F=m.Deferred();function H(){q.removeEventListener("DOMContentLoaded",H),n.removeEventListener("load",H),m.ready()}m.fn.ready=function(t){return F.then(t).catch((function(t){m.readyException(t)})),this},m.extend({isReady:!1,readyWait:1,ready:function(t){(!0===t?--m.readyWait:m.isReady)||(m.isReady=!0,!0!==t&&--m.readyWait>0||F.resolveWith(q,[m]))}}),m.ready.then=F.then,"complete"===q.readyState||"loading"!==q.readyState&&!q.documentElement.doScroll?n.setTimeout(m.ready):(q.addEventListener("DOMContentLoaded",H),n.addEventListener("load",H));var U=function(t,e,o,n,p,b,M){var z=0,r=t.length,c=null==o;if("object"===v(o))for(z in p=!0,o)U(t,e,z,o[z],!0,b,M);else if(void 0!==n&&(p=!0,A(n)||(M=!0),c&&(M?(e.call(t,n),e=null):(c=e,e=function(t,e,o){return c.call(m(t),o)})),e))for(;z1,null,!0)},removeData:function(t){return this.each((function(){Z.remove(this,t)}))}}),m.extend({queue:function(t,e,o){var n;if(t)return e=(e||"fx")+"queue",n=Q.get(t,e),o&&(!n||Array.isArray(o)?n=Q.access(t,e,m.makeArray(o)):n.push(o)),n||[]},dequeue:function(t,e){e=e||"fx";var o=m.queue(t,e),n=o.length,p=o.shift(),b=m._queueHooks(t,e);"inprogress"===p&&(p=o.shift(),n--),p&&("fx"===e&&o.unshift("inprogress"),delete b.stop,p.call(t,(function(){m.dequeue(t,e)}),b)),!n&&b&&b.empty.fire()},_queueHooks:function(t,e){var o=e+"queueHooks";return Q.get(t,o)||Q.access(t,o,{empty:m.Callbacks("once memory").add((function(){Q.remove(t,[e+"queue",o])}))})}}),m.fn.extend({queue:function(t,e){var o=2;return"string"!=typeof t&&(e=t,t="fx",o--),arguments.length\x20\t\r\n\f]*)/i,ft=/^$|^module$|\/(?:java|ecma)script/i;lt=q.createDocumentFragment().appendChild(q.createElement("div")),(dt=q.createElement("input")).setAttribute("type","radio"),dt.setAttribute("checked","checked"),dt.setAttribute("name","t"),lt.appendChild(dt),u.checkClone=lt.cloneNode(!0).cloneNode(!0).lastChild.checked,lt.innerHTML="",u.noCloneChecked=!!lt.cloneNode(!0).lastChild.defaultValue,lt.innerHTML="",u.option=!!lt.lastChild;var qt={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ht(t,e){var o;return o=void 0!==t.getElementsByTagName?t.getElementsByTagName(e||"*"):void 0!==t.querySelectorAll?t.querySelectorAll(e||"*"):[],void 0===e||e&&_(t,e)?m.merge([t],o):o}function Wt(t,e){for(var o=0,n=t.length;o",""]);var vt=/<|&#?\w+;/;function gt(t,e,o,n,p){for(var b,M,z,r,c,i,a=e.createDocumentFragment(),O=[],s=0,l=t.length;s-1)p&&p.push(b);else if(c=zt(b),M=ht(a.appendChild(b),"script"),c&&Wt(M),o)for(i=0;b=M[i++];)ft.test(b.type||"")&&o.push(b);return a}var mt=/^([^.]*)(?:\.(.+)|)/;function Rt(){return!0}function yt(){return!1}function Bt(t,e){return t===function(){try{return q.activeElement}catch(t){}}()==("focus"===e)}function Lt(t,e,o,n,p,b){var M,z;if("object"==typeof e){for(z in"string"!=typeof o&&(n=n||o,o=void 0),e)Lt(t,z,o,n,e[z],b);return t}if(null==n&&null==p?(p=o,n=o=void 0):null==p&&("string"==typeof o?(p=n,n=void 0):(p=n,n=o,o=void 0)),!1===p)p=yt;else if(!p)return t;return 1===b&&(M=p,p=function(t){return m().off(t),M.apply(this,arguments)},p.guid=M.guid||(M.guid=m.guid++)),t.each((function(){m.event.add(this,e,p,n,o)}))}function Xt(t,e,o){o?(Q.set(t,e,!1),m.event.add(t,e,{namespace:!1,handler:function(t){var n,p,b=Q.get(this,e);if(1&t.isTrigger&&this[e]){if(b.length)(m.event.special[e]||{}).delegateType&&t.stopPropagation();else if(b=z.call(arguments),Q.set(this,e,b),n=o(this,e),this[e](),b!==(p=Q.get(this,e))||n?Q.set(this,e,!1):p={},b!==p)return t.stopImmediatePropagation(),t.preventDefault(),p&&p.value}else b.length&&(Q.set(this,e,{value:m.event.trigger(m.extend(b[0],m.Event.prototype),b.slice(1),this)}),t.stopImmediatePropagation())}})):void 0===Q.get(t,e)&&m.event.add(t,e,Rt)}m.event={global:{},add:function(t,e,o,n,p){var b,M,z,r,c,i,a,O,s,l,d,u=Q.get(t);if(J(t))for(o.handler&&(o=(b=o).handler,p=b.selector),p&&m.find.matchesSelector(Mt,p),o.guid||(o.guid=m.guid++),(r=u.events)||(r=u.events=Object.create(null)),(M=u.handle)||(M=u.handle=function(e){return void 0!==m&&m.event.triggered!==e.type?m.event.dispatch.apply(t,arguments):void 0}),c=(e=(e||"").match(E)||[""]).length;c--;)s=d=(z=mt.exec(e[c])||[])[1],l=(z[2]||"").split(".").sort(),s&&(a=m.event.special[s]||{},s=(p?a.delegateType:a.bindType)||s,a=m.event.special[s]||{},i=m.extend({type:s,origType:d,data:n,handler:o,guid:o.guid,selector:p,needsContext:p&&m.expr.match.needsContext.test(p),namespace:l.join(".")},b),(O=r[s])||((O=r[s]=[]).delegateCount=0,a.setup&&!1!==a.setup.call(t,n,l,M)||t.addEventListener&&t.addEventListener(s,M)),a.add&&(a.add.call(t,i),i.handler.guid||(i.handler.guid=o.guid)),p?O.splice(O.delegateCount++,0,i):O.push(i),m.event.global[s]=!0)},remove:function(t,e,o,n,p){var b,M,z,r,c,i,a,O,s,l,d,u=Q.hasData(t)&&Q.get(t);if(u&&(r=u.events)){for(c=(e=(e||"").match(E)||[""]).length;c--;)if(s=d=(z=mt.exec(e[c])||[])[1],l=(z[2]||"").split(".").sort(),s){for(a=m.event.special[s]||{},O=r[s=(n?a.delegateType:a.bindType)||s]||[],z=z[2]&&new RegExp("(^|\\.)"+l.join("\\.(?:.*\\.|)")+"(\\.|$)"),M=b=O.length;b--;)i=O[b],!p&&d!==i.origType||o&&o.guid!==i.guid||z&&!z.test(i.namespace)||n&&n!==i.selector&&("**"!==n||!i.selector)||(O.splice(b,1),i.selector&&O.delegateCount--,a.remove&&a.remove.call(t,i));M&&!O.length&&(a.teardown&&!1!==a.teardown.call(t,l,u.handle)||m.removeEvent(t,s,u.handle),delete r[s])}else for(s in r)m.event.remove(t,s+e[c],o,n,!0);m.isEmptyObject(r)&&Q.remove(t,"handle events")}},dispatch:function(t){var e,o,n,p,b,M,z=new Array(arguments.length),r=m.event.fix(t),c=(Q.get(this,"events")||Object.create(null))[r.type]||[],i=m.event.special[r.type]||{};for(z[0]=r,e=1;e=1))for(;c!==this;c=c.parentNode||this)if(1===c.nodeType&&("click"!==t.type||!0!==c.disabled)){for(b=[],M={},o=0;o-1:m.find(p,this,null,[c]).length),M[p]&&b.push(n);b.length&&z.push({elem:c,handlers:b})}return c=this,r\s*$/g;function xt(t,e){return _(t,"table")&&_(11!==e.nodeType?e:e.firstChild,"tr")&&m(t).children("tbody")[0]||t}function Tt(t){return t.type=(null!==t.getAttribute("type"))+"/"+t.type,t}function Ct(t){return"true/"===(t.type||"").slice(0,5)?t.type=t.type.slice(5):t.removeAttribute("type"),t}function St(t,e){var o,n,p,b,M,z;if(1===e.nodeType){if(Q.hasData(t)&&(z=Q.get(t).events))for(p in Q.remove(e,"handle events"),z)for(o=0,n=z[p].length;o1&&"string"==typeof l&&!u.checkClone&&Nt.test(l))return t.each((function(p){var b=t.eq(p);d&&(e[0]=l.call(this,p,b.html())),Et(b,e,o,n)}));if(O&&(b=(p=gt(e,t[0].ownerDocument,!1,t,n)).firstChild,1===p.childNodes.length&&(p=b),b||n)){for(z=(M=m.map(ht(p,"script"),Tt)).length;a0&&Wt(M,!r&&ht(t,"script")),z},cleanData:function(t){for(var e,o,n,p=m.event.special,b=0;void 0!==(o=t[b]);b++)if(J(o)){if(e=o[Q.expando]){if(e.events)for(n in e.events)p[n]?m.event.remove(o,n):m.removeEvent(o,n,e.handle);o[Q.expando]=void 0}o[Z.expando]&&(o[Z.expando]=void 0)}}}),m.fn.extend({detach:function(t){return Dt(this,t,!0)},remove:function(t){return Dt(this,t)},text:function(t){return U(this,(function(t){return void 0===t?m.text(this):this.empty().each((function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)}))}),null,t,arguments.length)},append:function(){return Et(this,arguments,(function(t){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||xt(this,t).appendChild(t)}))},prepend:function(){return Et(this,arguments,(function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=xt(this,t);e.insertBefore(t,e.firstChild)}}))},before:function(){return Et(this,arguments,(function(t){this.parentNode&&this.parentNode.insertBefore(t,this)}))},after:function(){return Et(this,arguments,(function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)}))},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(m.cleanData(ht(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map((function(){return m.clone(this,t,e)}))},html:function(t){return U(this,(function(t){var e=this[0]||{},o=0,n=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!_t.test(t)&&!qt[(At.exec(t)||["",""])[1].toLowerCase()]){t=m.htmlPrefilter(t);try{for(;o=0&&(r+=Math.max(0,Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-b-r-z-.5))||0),r}function ne(t,e,o){var n=It(t),p=(!u.boxSizingReliable()||o)&&"border-box"===m.css(t,"boxSizing",!1,n),b=p,M=Vt(t,e,n),z="offset"+e[0].toUpperCase()+e.slice(1);if(Pt.test(M)){if(!o)return M;M="auto"}return(!u.boxSizingReliable()&&p||!u.reliableTrDimensions()&&_(t,"tr")||"auto"===M||!parseFloat(M)&&"inline"===m.css(t,"display",!1,n))&&t.getClientRects().length&&(p="border-box"===m.css(t,"boxSizing",!1,n),(b=z in t)&&(M=t[z])),(M=parseFloat(M)||0)+oe(t,e,o||(p?"border":"content"),b,n,M)+"px"}function pe(t,e,o,n,p){return new pe.prototype.init(t,e,o,n,p)}m.extend({cssHooks:{opacity:{get:function(t,e){if(e){var o=Vt(t,"opacity");return""===o?"1":o}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(t,e,o,n){if(t&&3!==t.nodeType&&8!==t.nodeType&&t.style){var p,b,M,z=G(e),r=jt.test(e),c=t.style;if(r||(e=Kt(z)),M=m.cssHooks[e]||m.cssHooks[z],void 0===o)return M&&"get"in M&&void 0!==(p=M.get(t,!1,n))?p:c[e];"string"===(b=typeof o)&&(p=pt.exec(o))&&p[1]&&(o=it(t,e,p),b="number"),null!=o&&o==o&&("number"!==b||r||(o+=p&&p[3]||(m.cssNumber[z]?"":"px")),u.clearCloneStyle||""!==o||0!==e.indexOf("background")||(c[e]="inherit"),M&&"set"in M&&void 0===(o=M.set(t,o,n))||(r?c.setProperty(e,o):c[e]=o))}},css:function(t,e,o,n){var p,b,M,z=G(e);return jt.test(e)||(e=Kt(z)),(M=m.cssHooks[e]||m.cssHooks[z])&&"get"in M&&(p=M.get(t,!0,o)),void 0===p&&(p=Vt(t,e,n)),"normal"===p&&e in te&&(p=te[e]),""===o||o?(b=parseFloat(p),!0===o||isFinite(b)?b||0:p):p}}),m.each(["height","width"],(function(t,e){m.cssHooks[e]={get:function(t,o,n){if(o)return!Qt.test(m.css(t,"display"))||t.getClientRects().length&&t.getBoundingClientRect().width?ne(t,e,n):Ft(t,Zt,(function(){return ne(t,e,n)}))},set:function(t,o,n){var p,b=It(t),M=!u.scrollboxSize()&&"absolute"===b.position,z=(M||n)&&"border-box"===m.css(t,"boxSizing",!1,b),r=n?oe(t,e,n,z,b):0;return z&&M&&(r-=Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-parseFloat(b[e])-oe(t,e,"border",!1,b)-.5)),r&&(p=pt.exec(o))&&"px"!==(p[3]||"px")&&(t.style[e]=o,o=m.css(t,e)),ee(0,o,r)}}})),m.cssHooks.marginLeft=$t(u.reliableMarginLeft,(function(t,e){if(e)return(parseFloat(Vt(t,"marginLeft"))||t.getBoundingClientRect().left-Ft(t,{marginLeft:0},(function(){return t.getBoundingClientRect().left})))+"px"})),m.each({margin:"",padding:"",border:"Width"},(function(t,e){m.cssHooks[t+e]={expand:function(o){for(var n=0,p={},b="string"==typeof o?o.split(" "):[o];n<4;n++)p[t+bt[n]+e]=b[n]||b[n-2]||b[0];return p}},"margin"!==t&&(m.cssHooks[t+e].set=ee)})),m.fn.extend({css:function(t,e){return U(this,(function(t,e,o){var n,p,b={},M=0;if(Array.isArray(e)){for(n=It(t),p=e.length;M1)}}),m.Tween=pe,pe.prototype={constructor:pe,init:function(t,e,o,n,p,b){this.elem=t,this.prop=o,this.easing=p||m.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=n,this.unit=b||(m.cssNumber[o]?"":"px")},cur:function(){var t=pe.propHooks[this.prop];return t&&t.get?t.get(this):pe.propHooks._default.get(this)},run:function(t){var e,o=pe.propHooks[this.prop];return this.options.duration?this.pos=e=m.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),o&&o.set?o.set(this):pe.propHooks._default.set(this),this}},pe.prototype.init.prototype=pe.prototype,pe.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=m.css(t.elem,t.prop,""))&&"auto"!==e?e:0},set:function(t){m.fx.step[t.prop]?m.fx.step[t.prop](t):1!==t.elem.nodeType||!m.cssHooks[t.prop]&&null==t.elem.style[Kt(t.prop)]?t.elem[t.prop]=t.now:m.style(t.elem,t.prop,t.now+t.unit)}}},pe.propHooks.scrollTop=pe.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},m.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},m.fx=pe.prototype.init,m.fx.step={};var be,Me,ze=/^(?:toggle|show|hide)$/,re=/queueHooks$/;function ce(){Me&&(!1===q.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(ce):n.setTimeout(ce,m.fx.interval),m.fx.tick())}function ie(){return n.setTimeout((function(){be=void 0})),be=Date.now()}function ae(t,e){var o,n=0,p={height:t};for(e=e?1:0;n<4;n+=2-e)p["margin"+(o=bt[n])]=p["padding"+o]=t;return e&&(p.opacity=p.width=t),p}function Oe(t,e,o){for(var n,p=(se.tweeners[e]||[]).concat(se.tweeners["*"]),b=0,M=p.length;b1)},removeAttr:function(t){return this.each((function(){m.removeAttr(this,t)}))}}),m.extend({attr:function(t,e,o){var n,p,b=t.nodeType;if(3!==b&&8!==b&&2!==b)return void 0===t.getAttribute?m.prop(t,e,o):(1===b&&m.isXMLDoc(t)||(p=m.attrHooks[e.toLowerCase()]||(m.expr.match.bool.test(e)?le:void 0)),void 0!==o?null===o?void m.removeAttr(t,e):p&&"set"in p&&void 0!==(n=p.set(t,o,e))?n:(t.setAttribute(e,o+""),o):p&&"get"in p&&null!==(n=p.get(t,e))?n:null==(n=m.find.attr(t,e))?void 0:n)},attrHooks:{type:{set:function(t,e){if(!u.radioValue&&"radio"===e&&_(t,"input")){var o=t.value;return t.setAttribute("type",e),o&&(t.value=o),e}}}},removeAttr:function(t,e){var o,n=0,p=e&&e.match(E);if(p&&1===t.nodeType)for(;o=p[n++];)t.removeAttribute(o)}}),le={set:function(t,e,o){return!1===e?m.removeAttr(t,o):t.setAttribute(o,o),o}},m.each(m.expr.match.bool.source.match(/\w+/g),(function(t,e){var o=de[e]||m.find.attr;de[e]=function(t,e,n){var p,b,M=e.toLowerCase();return n||(b=de[M],de[M]=p,p=null!=o(t,e,n)?M:null,de[M]=b),p}}));var ue=/^(?:input|select|textarea|button)$/i,Ae=/^(?:a|area)$/i;function fe(t){return(t.match(E)||[]).join(" ")}function qe(t){return t.getAttribute&&t.getAttribute("class")||""}function he(t){return Array.isArray(t)?t:"string"==typeof t&&t.match(E)||[]}m.fn.extend({prop:function(t,e){return U(this,m.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each((function(){delete this[m.propFix[t]||t]}))}}),m.extend({prop:function(t,e,o){var n,p,b=t.nodeType;if(3!==b&&8!==b&&2!==b)return 1===b&&m.isXMLDoc(t)||(e=m.propFix[e]||e,p=m.propHooks[e]),void 0!==o?p&&"set"in p&&void 0!==(n=p.set(t,o,e))?n:t[e]=o:p&&"get"in p&&null!==(n=p.get(t,e))?n:t[e]},propHooks:{tabIndex:{get:function(t){var e=m.find.attr(t,"tabindex");return e?parseInt(e,10):ue.test(t.nodeName)||Ae.test(t.nodeName)&&t.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),u.optSelected||(m.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),m.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],(function(){m.propFix[this.toLowerCase()]=this})),m.fn.extend({addClass:function(t){var e,o,n,p,b,M;return A(t)?this.each((function(e){m(this).addClass(t.call(this,e,qe(this)))})):(e=he(t)).length?this.each((function(){if(n=qe(this),o=1===this.nodeType&&" "+fe(n)+" "){for(b=0;b-1;)o=o.replace(" "+p+" "," ");M=fe(o),n!==M&&this.setAttribute("class",M)}})):this:this.attr("class","")},toggleClass:function(t,e){var o,n,p,b,M=typeof t,z="string"===M||Array.isArray(t);return A(t)?this.each((function(o){m(this).toggleClass(t.call(this,o,qe(this),e),e)})):"boolean"==typeof e&&z?e?this.addClass(t):this.removeClass(t):(o=he(t),this.each((function(){if(z)for(b=m(this),p=0;p-1)return!0;return!1}});var We=/\r/g;m.fn.extend({val:function(t){var e,o,n,p=this[0];return arguments.length?(n=A(t),this.each((function(o){var p;1===this.nodeType&&(null==(p=n?t.call(this,o,m(this).val()):t)?p="":"number"==typeof p?p+="":Array.isArray(p)&&(p=m.map(p,(function(t){return null==t?"":t+""}))),(e=m.valHooks[this.type]||m.valHooks[this.nodeName.toLowerCase()])&&"set"in e&&void 0!==e.set(this,p,"value")||(this.value=p))}))):p?(e=m.valHooks[p.type]||m.valHooks[p.nodeName.toLowerCase()])&&"get"in e&&void 0!==(o=e.get(p,"value"))?o:"string"==typeof(o=p.value)?o.replace(We,""):null==o?"":o:void 0}}),m.extend({valHooks:{option:{get:function(t){var e=m.find.attr(t,"value");return null!=e?e:fe(m.text(t))}},select:{get:function(t){var e,o,n,p=t.options,b=t.selectedIndex,M="select-one"===t.type,z=M?null:[],r=M?b+1:p.length;for(n=b<0?r:M?b:0;n-1)&&(o=!0);return o||(t.selectedIndex=-1),b}}}}),m.each(["radio","checkbox"],(function(){m.valHooks[this]={set:function(t,e){if(Array.isArray(e))return t.checked=m.inArray(m(t).val(),e)>-1}},u.checkOn||(m.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})})),u.focusin="onfocusin"in n;var ve=/^(?:focusinfocus|focusoutblur)$/,ge=function(t){t.stopPropagation()};m.extend(m.event,{trigger:function(t,e,o,p){var b,M,z,r,c,i,a,O,l=[o||q],d=s.call(t,"type")?t.type:t,u=s.call(t,"namespace")?t.namespace.split("."):[];if(M=O=z=o=o||q,3!==o.nodeType&&8!==o.nodeType&&!ve.test(d+m.event.triggered)&&(d.indexOf(".")>-1&&(u=d.split("."),d=u.shift(),u.sort()),c=d.indexOf(":")<0&&"on"+d,(t=t[m.expando]?t:new m.Event(d,"object"==typeof t&&t)).isTrigger=p?2:3,t.namespace=u.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+u.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=o),e=null==e?[t]:m.makeArray(e,[t]),a=m.event.special[d]||{},p||!a.trigger||!1!==a.trigger.apply(o,e))){if(!p&&!a.noBubble&&!f(o)){for(r=a.delegateType||d,ve.test(r+d)||(M=M.parentNode);M;M=M.parentNode)l.push(M),z=M;z===(o.ownerDocument||q)&&l.push(z.defaultView||z.parentWindow||n)}for(b=0;(M=l[b++])&&!t.isPropagationStopped();)O=M,t.type=b>1?r:a.bindType||d,(i=(Q.get(M,"events")||Object.create(null))[t.type]&&Q.get(M,"handle"))&&i.apply(M,e),(i=c&&M[c])&&i.apply&&J(M)&&(t.result=i.apply(M,e),!1===t.result&&t.preventDefault());return t.type=d,p||t.isDefaultPrevented()||a._default&&!1!==a._default.apply(l.pop(),e)||!J(o)||c&&A(o[d])&&!f(o)&&((z=o[c])&&(o[c]=null),m.event.triggered=d,t.isPropagationStopped()&&O.addEventListener(d,ge),o[d](),t.isPropagationStopped()&&O.removeEventListener(d,ge),m.event.triggered=void 0,z&&(o[c]=z)),t.result}},simulate:function(t,e,o){var n=m.extend(new m.Event,o,{type:t,isSimulated:!0});m.event.trigger(n,null,e)}}),m.fn.extend({trigger:function(t,e){return this.each((function(){m.event.trigger(t,e,this)}))},triggerHandler:function(t,e){var o=this[0];if(o)return m.event.trigger(t,e,o,!0)}}),u.focusin||m.each({focus:"focusin",blur:"focusout"},(function(t,e){var o=function(t){m.event.simulate(e,t.target,m.event.fix(t))};m.event.special[e]={setup:function(){var n=this.ownerDocument||this.document||this,p=Q.access(n,e);p||n.addEventListener(t,o,!0),Q.access(n,e,(p||0)+1)},teardown:function(){var n=this.ownerDocument||this.document||this,p=Q.access(n,e)-1;p?Q.access(n,e,p):(n.removeEventListener(t,o,!0),Q.remove(n,e))}}}));var me=n.location,Re={guid:Date.now()},ye=/\?/;m.parseXML=function(t){var e,o;if(!t||"string"!=typeof t)return null;try{e=(new n.DOMParser).parseFromString(t,"text/xml")}catch(t){}return o=e&&e.getElementsByTagName("parsererror")[0],e&&!o||m.error("Invalid XML: "+(o?m.map(o.childNodes,(function(t){return t.textContent})).join("\n"):t)),e};var Be=/\[\]$/,Le=/\r?\n/g,Xe=/^(?:submit|button|image|reset|file)$/i,_e=/^(?:input|select|textarea|keygen)/i;function Ne(t,e,o,n){var p;if(Array.isArray(e))m.each(e,(function(e,p){o||Be.test(t)?n(t,p):Ne(t+"["+("object"==typeof p&&null!=p?e:"")+"]",p,o,n)}));else if(o||"object"!==v(e))n(t,e);else for(p in e)Ne(t+"["+p+"]",e[p],o,n)}m.param=function(t,e){var o,n=[],p=function(t,e){var o=A(e)?e():e;n[n.length]=encodeURIComponent(t)+"="+encodeURIComponent(null==o?"":o)};if(null==t)return"";if(Array.isArray(t)||t.jquery&&!m.isPlainObject(t))m.each(t,(function(){p(this.name,this.value)}));else for(o in t)Ne(o,t[o],e,p);return n.join("&")},m.fn.extend({serialize:function(){return m.param(this.serializeArray())},serializeArray:function(){return this.map((function(){var t=m.prop(this,"elements");return t?m.makeArray(t):this})).filter((function(){var t=this.type;return this.name&&!m(this).is(":disabled")&&_e.test(this.nodeName)&&!Xe.test(t)&&(this.checked||!ut.test(t))})).map((function(t,e){var o=m(this).val();return null==o?null:Array.isArray(o)?m.map(o,(function(t){return{name:e.name,value:t.replace(Le,"\r\n")}})):{name:e.name,value:o.replace(Le,"\r\n")}})).get()}});var we=/%20/g,xe=/#.*$/,Te=/([?&])_=[^&]*/,Ce=/^(.*?):[ \t]*([^\r\n]*)$/gm,Se=/^(?:GET|HEAD)$/,ke=/^\/\//,Ee={},De={},Pe="*/".concat("*"),je=q.createElement("a");function Ie(t){return function(e,o){"string"!=typeof e&&(o=e,e="*");var n,p=0,b=e.toLowerCase().match(E)||[];if(A(o))for(;n=b[p++];)"+"===n[0]?(n=n.slice(1)||"*",(t[n]=t[n]||[]).unshift(o)):(t[n]=t[n]||[]).push(o)}}function Fe(t,e,o,n){var p={},b=t===De;function M(z){var r;return p[z]=!0,m.each(t[z]||[],(function(t,z){var c=z(e,o,n);return"string"!=typeof c||b||p[c]?b?!(r=c):void 0:(e.dataTypes.unshift(c),M(c),!1)})),r}return M(e.dataTypes[0])||!p["*"]&&M("*")}function He(t,e){var o,n,p=m.ajaxSettings.flatOptions||{};for(o in e)void 0!==e[o]&&((p[o]?t:n||(n={}))[o]=e[o]);return n&&m.extend(!0,t,n),t}je.href=me.href,m.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:me.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(me.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Pe,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":m.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?He(He(t,m.ajaxSettings),e):He(m.ajaxSettings,t)},ajaxPrefilter:Ie(Ee),ajaxTransport:Ie(De),ajax:function(t,e){"object"==typeof t&&(e=t,t=void 0),e=e||{};var o,p,b,M,z,r,c,i,a,O,s=m.ajaxSetup({},e),l=s.context||s,d=s.context&&(l.nodeType||l.jquery)?m(l):m.event,u=m.Deferred(),A=m.Callbacks("once memory"),f=s.statusCode||{},h={},W={},v="canceled",g={readyState:0,getResponseHeader:function(t){var e;if(c){if(!M)for(M={};e=Ce.exec(b);)M[e[1].toLowerCase()+" "]=(M[e[1].toLowerCase()+" "]||[]).concat(e[2]);e=M[t.toLowerCase()+" "]}return null==e?null:e.join(", ")},getAllResponseHeaders:function(){return c?b:null},setRequestHeader:function(t,e){return null==c&&(t=W[t.toLowerCase()]=W[t.toLowerCase()]||t,h[t]=e),this},overrideMimeType:function(t){return null==c&&(s.mimeType=t),this},statusCode:function(t){var e;if(t)if(c)g.always(t[g.status]);else for(e in t)f[e]=[f[e],t[e]];return this},abort:function(t){var e=t||v;return o&&o.abort(e),R(0,e),this}};if(u.promise(g),s.url=((t||s.url||me.href)+"").replace(ke,me.protocol+"//"),s.type=e.method||e.type||s.method||s.type,s.dataTypes=(s.dataType||"*").toLowerCase().match(E)||[""],null==s.crossDomain){r=q.createElement("a");try{r.href=s.url,r.href=r.href,s.crossDomain=je.protocol+"//"+je.host!=r.protocol+"//"+r.host}catch(t){s.crossDomain=!0}}if(s.data&&s.processData&&"string"!=typeof s.data&&(s.data=m.param(s.data,s.traditional)),Fe(Ee,s,e,g),c)return g;for(a in(i=m.event&&s.global)&&0==m.active++&&m.event.trigger("ajaxStart"),s.type=s.type.toUpperCase(),s.hasContent=!Se.test(s.type),p=s.url.replace(xe,""),s.hasContent?s.data&&s.processData&&0===(s.contentType||"").indexOf("application/x-www-form-urlencoded")&&(s.data=s.data.replace(we,"+")):(O=s.url.slice(p.length),s.data&&(s.processData||"string"==typeof s.data)&&(p+=(ye.test(p)?"&":"?")+s.data,delete s.data),!1===s.cache&&(p=p.replace(Te,"$1"),O=(ye.test(p)?"&":"?")+"_="+Re.guid+++O),s.url=p+O),s.ifModified&&(m.lastModified[p]&&g.setRequestHeader("If-Modified-Since",m.lastModified[p]),m.etag[p]&&g.setRequestHeader("If-None-Match",m.etag[p])),(s.data&&s.hasContent&&!1!==s.contentType||e.contentType)&&g.setRequestHeader("Content-Type",s.contentType),g.setRequestHeader("Accept",s.dataTypes[0]&&s.accepts[s.dataTypes[0]]?s.accepts[s.dataTypes[0]]+("*"!==s.dataTypes[0]?", "+Pe+"; q=0.01":""):s.accepts["*"]),s.headers)g.setRequestHeader(a,s.headers[a]);if(s.beforeSend&&(!1===s.beforeSend.call(l,g,s)||c))return g.abort();if(v="abort",A.add(s.complete),g.done(s.success),g.fail(s.error),o=Fe(De,s,e,g)){if(g.readyState=1,i&&d.trigger("ajaxSend",[g,s]),c)return g;s.async&&s.timeout>0&&(z=n.setTimeout((function(){g.abort("timeout")}),s.timeout));try{c=!1,o.send(h,R)}catch(t){if(c)throw t;R(-1,t)}}else R(-1,"No Transport");function R(t,e,M,r){var a,O,q,h,W,v=e;c||(c=!0,z&&n.clearTimeout(z),o=void 0,b=r||"",g.readyState=t>0?4:0,a=t>=200&&t<300||304===t,M&&(h=function(t,e,o){for(var n,p,b,M,z=t.contents,r=t.dataTypes;"*"===r[0];)r.shift(),void 0===n&&(n=t.mimeType||e.getResponseHeader("Content-Type"));if(n)for(p in z)if(z[p]&&z[p].test(n)){r.unshift(p);break}if(r[0]in o)b=r[0];else{for(p in o){if(!r[0]||t.converters[p+" "+r[0]]){b=p;break}M||(M=p)}b=b||M}if(b)return b!==r[0]&&r.unshift(b),o[b]}(s,g,M)),!a&&m.inArray("script",s.dataTypes)>-1&&m.inArray("json",s.dataTypes)<0&&(s.converters["text script"]=function(){}),h=function(t,e,o,n){var p,b,M,z,r,c={},i=t.dataTypes.slice();if(i[1])for(M in t.converters)c[M.toLowerCase()]=t.converters[M];for(b=i.shift();b;)if(t.responseFields[b]&&(o[t.responseFields[b]]=e),!r&&n&&t.dataFilter&&(e=t.dataFilter(e,t.dataType)),r=b,b=i.shift())if("*"===b)b=r;else if("*"!==r&&r!==b){if(!(M=c[r+" "+b]||c["* "+b]))for(p in c)if((z=p.split(" "))[1]===b&&(M=c[r+" "+z[0]]||c["* "+z[0]])){!0===M?M=c[p]:!0!==c[p]&&(b=z[0],i.unshift(z[1]));break}if(!0!==M)if(M&&t.throws)e=M(e);else try{e=M(e)}catch(t){return{state:"parsererror",error:M?t:"No conversion from "+r+" to "+b}}}return{state:"success",data:e}}(s,h,g,a),a?(s.ifModified&&((W=g.getResponseHeader("Last-Modified"))&&(m.lastModified[p]=W),(W=g.getResponseHeader("etag"))&&(m.etag[p]=W)),204===t||"HEAD"===s.type?v="nocontent":304===t?v="notmodified":(v=h.state,O=h.data,a=!(q=h.error))):(q=v,!t&&v||(v="error",t<0&&(t=0))),g.status=t,g.statusText=(e||v)+"",a?u.resolveWith(l,[O,v,g]):u.rejectWith(l,[g,v,q]),g.statusCode(f),f=void 0,i&&d.trigger(a?"ajaxSuccess":"ajaxError",[g,s,a?O:q]),A.fireWith(l,[g,v]),i&&(d.trigger("ajaxComplete",[g,s]),--m.active||m.event.trigger("ajaxStop")))}return g},getJSON:function(t,e,o){return m.get(t,e,o,"json")},getScript:function(t,e){return m.get(t,void 0,e,"script")}}),m.each(["get","post"],(function(t,e){m[e]=function(t,o,n,p){return A(o)&&(p=p||n,n=o,o=void 0),m.ajax(m.extend({url:t,type:e,dataType:p,data:o,success:n},m.isPlainObject(t)&&t))}})),m.ajaxPrefilter((function(t){var e;for(e in t.headers)"content-type"===e.toLowerCase()&&(t.contentType=t.headers[e]||"")})),m._evalUrl=function(t,e,o){return m.ajax({url:t,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,converters:{"text script":function(){}},dataFilter:function(t){m.globalEval(t,e,o)}})},m.fn.extend({wrapAll:function(t){var e;return this[0]&&(A(t)&&(t=t.call(this[0])),e=m(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map((function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t})).append(this)),this},wrapInner:function(t){return A(t)?this.each((function(e){m(this).wrapInner(t.call(this,e))})):this.each((function(){var e=m(this),o=e.contents();o.length?o.wrapAll(t):e.append(t)}))},wrap:function(t){var e=A(t);return this.each((function(o){m(this).wrapAll(e?t.call(this,o):t)}))},unwrap:function(t){return this.parent(t).not("body").each((function(){m(this).replaceWith(this.childNodes)})),this}}),m.expr.pseudos.hidden=function(t){return!m.expr.pseudos.visible(t)},m.expr.pseudos.visible=function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)},m.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(t){}};var Ue={0:200,1223:204},Ve=m.ajaxSettings.xhr();u.cors=!!Ve&&"withCredentials"in Ve,u.ajax=Ve=!!Ve,m.ajaxTransport((function(t){var e,o;if(u.cors||Ve&&!t.crossDomain)return{send:function(p,b){var M,z=t.xhr();if(z.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(M in t.xhrFields)z[M]=t.xhrFields[M];for(M in t.mimeType&&z.overrideMimeType&&z.overrideMimeType(t.mimeType),t.crossDomain||p["X-Requested-With"]||(p["X-Requested-With"]="XMLHttpRequest"),p)z.setRequestHeader(M,p[M]);e=function(t){return function(){e&&(e=o=z.onload=z.onerror=z.onabort=z.ontimeout=z.onreadystatechange=null,"abort"===t?z.abort():"error"===t?"number"!=typeof z.status?b(0,"error"):b(z.status,z.statusText):b(Ue[z.status]||z.status,z.statusText,"text"!==(z.responseType||"text")||"string"!=typeof z.responseText?{binary:z.response}:{text:z.responseText},z.getAllResponseHeaders()))}},z.onload=e(),o=z.onerror=z.ontimeout=e("error"),void 0!==z.onabort?z.onabort=o:z.onreadystatechange=function(){4===z.readyState&&n.setTimeout((function(){e&&o()}))},e=e("abort");try{z.send(t.hasContent&&t.data||null)}catch(t){if(e)throw t}},abort:function(){e&&e()}}})),m.ajaxPrefilter((function(t){t.crossDomain&&(t.contents.script=!1)})),m.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return m.globalEval(t),t}}}),m.ajaxPrefilter("script",(function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET")})),m.ajaxTransport("script",(function(t){var e,o;if(t.crossDomain||t.scriptAttrs)return{send:function(n,p){e=m("