mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-01-17 10:08:05 +00:00
First basic statistics graph generation using Chart.js
This commit is contained in:
parent
0729718ccf
commit
f8bde4345f
25 changed files with 496 additions and 504 deletions
|
|
@ -21,6 +21,7 @@ namespace App\Http\Controllers\Admin;
|
|||
|
||||
use App\ContactsList;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ContactsListController extends Controller
|
||||
|
|
@ -49,8 +50,7 @@ class ContactsListController extends Controller
|
|||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required',
|
||||
'description' => 'required'
|
||||
'title' => 'required|unique:contacts_lists'
|
||||
]);
|
||||
|
||||
$contactsList = new ContactsList;
|
||||
|
|
@ -71,8 +71,10 @@ class ContactsListController extends Controller
|
|||
public function update(Request $request, int $id)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required',
|
||||
'description' => 'required'
|
||||
'title' => [
|
||||
'required',
|
||||
Rule::unique('contacts_lists')->ignore($id),
|
||||
],
|
||||
]);
|
||||
|
||||
$contactsList = ContactsList::findOrFail($id);
|
||||
|
|
|
|||
|
|
@ -2,52 +2,218 @@
|
|||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Account;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use App\Libraries\StatisticsCruncher;
|
||||
use App\StatisticsMessage;
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonInterval;
|
||||
use Carbon\CarbonPeriod;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class StatisticsController extends Controller
|
||||
{
|
||||
public function showDay(Request $request)
|
||||
public function index(Request $request)
|
||||
{
|
||||
$day = StatisticsCruncher::day();
|
||||
$maxDay = 0;
|
||||
foreach ($day as $hour) {
|
||||
if ($maxDay < $hour['all']) $maxDay = $hour['all'];
|
||||
}
|
||||
|
||||
return view('admin.statistics.show_day', [
|
||||
'day' => $day,
|
||||
'max_day' => $maxDay,
|
||||
return redirect()->route('admin.statistics.show', [
|
||||
'type' => 'messages'
|
||||
]);
|
||||
}
|
||||
|
||||
public function showWeek(Request $request)
|
||||
public function edit(Request $request)
|
||||
{
|
||||
$week = StatisticsCruncher::week();
|
||||
$maxWeek = 0;
|
||||
foreach ($week as $day) {
|
||||
if ($maxWeek < $day['all']) $maxWeek = $day['all'];
|
||||
}
|
||||
|
||||
return view('admin.statistics.show_week', [
|
||||
'week' => $week,
|
||||
'max_week' => $maxWeek,
|
||||
return redirect()->route('admin.statistics.show', [
|
||||
'from' => $request->get('from'),
|
||||
'type' => $request->get('type'),
|
||||
'to' => $request->get('to'),
|
||||
'by' => $request->get('by'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function showMonth(Request $request)
|
||||
public function show(Request $request, string $type = 'messages')
|
||||
{
|
||||
$month = StatisticsCruncher::month();
|
||||
$maxMonth = 0;
|
||||
foreach ($month as $day) {
|
||||
if ($maxMonth < $day['all']) $maxMonth = $day['all'];
|
||||
$request->validate([
|
||||
'from' => 'date_format:Y-m-d|before:to',
|
||||
'to' => 'date_format:Y-m-d|after:from',
|
||||
'by' => 'in:day,week,month,year',
|
||||
]);
|
||||
|
||||
$dateColumn = 'created_at';
|
||||
$label = 'Label';
|
||||
|
||||
switch ($type) {
|
||||
case 'messages':
|
||||
$dateColumn = 'sent_at';
|
||||
$label = 'Messages';
|
||||
$data = StatisticsMessage::orderBy($dateColumn, 'asc');
|
||||
break;
|
||||
|
||||
case 'accounts':
|
||||
$label = 'Accounts';
|
||||
$data = Account::orderBy($dateColumn, 'asc');
|
||||
break;
|
||||
}
|
||||
|
||||
return view('admin.statistics.show_month', [
|
||||
'month' => $month,
|
||||
'max_month' => $maxMonth,
|
||||
$data = $data->groupBy('moment')
|
||||
->orderBy('moment', 'desc')
|
||||
->setEagerLoads([]);
|
||||
|
||||
if ($request->get('to')) {
|
||||
$data = $data->where($dateColumn, '<=', $request->get('to'));
|
||||
}
|
||||
|
||||
$by = $request->get('by', 'day');
|
||||
|
||||
switch ($by) {
|
||||
case 'day':
|
||||
$data = $data->where($dateColumn, '>=', $request->get('from', Carbon::now()->subDay()->format('Y-m-d H:i:s')))
|
||||
->get([
|
||||
DB::raw("date_format(" . $dateColumn . ",'%Y-%m-%d %H') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
]);
|
||||
break;
|
||||
case 'week':
|
||||
$data = $data->where($dateColumn, '>=', $request->get('from', Carbon::now()->subWeek()->format('Y-m-d H:i:s')))
|
||||
->get([
|
||||
DB::raw("date_format(" . $dateColumn . ",'%Y-%m-%d') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
]);
|
||||
break;
|
||||
case 'month':
|
||||
$data = $data->where($dateColumn, '>=', $request->get('from', Carbon::now()->subMonth()->format('Y-m-d H:i:s')))
|
||||
->get([
|
||||
DB::raw("date_format(" . $dateColumn . ",'%Y-%m-%d') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
]);
|
||||
break;
|
||||
case 'year':
|
||||
$data = $data->where($dateColumn, '>=', $request->get('from', Carbon::now()->subYear()->format('Y-m-d H:i:s')))
|
||||
->get([
|
||||
DB::raw("date_format(" . $dateColumn . ",'%Y-%m') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
$data = $data->each->setAppends([])->pluck('count', 'moment');
|
||||
|
||||
$data = $this->compileStatistics(
|
||||
$by,
|
||||
$request->get('from'),
|
||||
$request->get('to'),
|
||||
$data
|
||||
);
|
||||
|
||||
if ($request->get('export', false)) {
|
||||
$file = fopen('php://output', 'w');
|
||||
|
||||
$callback = function () use ($data, $file) {
|
||||
foreach ($data as $key => $value) {
|
||||
fputcsv($file, [$key, $value]);
|
||||
}
|
||||
|
||||
fclose($file);
|
||||
};
|
||||
|
||||
return response()->stream($callback, 200, [
|
||||
"Content-type" => "text/csv",
|
||||
"Content-Disposition" => "attachment; filename=export.csv",
|
||||
"Pragma" => "no-cache",
|
||||
"Cache-Control" => "must-revalidate, post-check=0, pre-check=0",
|
||||
"Expires" => "0"
|
||||
]);
|
||||
}
|
||||
|
||||
$config = [
|
||||
'type' => 'bar',
|
||||
'data' => [
|
||||
'labels' => $data->keys()->toArray(),
|
||||
'datasets' => [[
|
||||
'label' => $label,
|
||||
'borderColor' => 'rgba(108, 122, 135, 1)',
|
||||
'backgroundColor' => 'rgba(108, 122, 135, 1)',
|
||||
'data' => $data->values()->toArray(),
|
||||
'order' => 1
|
||||
]]
|
||||
],
|
||||
'options' => [
|
||||
'maintainAspectRatio' => false,
|
||||
'spanGaps' => true,
|
||||
'legend' => [
|
||||
'position' => 'right'
|
||||
],
|
||||
'scales' => [
|
||||
'y' => [
|
||||
'stacked' => true,
|
||||
'title' => [
|
||||
'display' => true,
|
||||
'text' => $label
|
||||
]
|
||||
],
|
||||
'x' => [
|
||||
'stacked' => true,
|
||||
]
|
||||
],
|
||||
'interaction' => [
|
||||
'mode' => 'nearest',
|
||||
'axis' => 'x',
|
||||
'intersect' => false
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
return view('admin.statistics.show', [
|
||||
'jsonConfig' => json_encode($config),
|
||||
'by' => $by,
|
||||
'type' => $type,
|
||||
'request' => $request
|
||||
]);
|
||||
}
|
||||
|
||||
private static function compileStatistics(string $by, $from, $to, $data): Collection
|
||||
{
|
||||
$stats = [];
|
||||
|
||||
switch ($by) {
|
||||
case 'day':
|
||||
$period = collect(CarbonInterval::hour()->toPeriod(
|
||||
$from ?? Carbon::now()->subDay()->format('Y-m-d H:i:s'),
|
||||
$to ?? Carbon::now()->format('Y-m-d H:i:s')
|
||||
))->map->format('Y-m-d H');
|
||||
break;
|
||||
|
||||
case 'week':
|
||||
$period = collect(CarbonPeriod::create(
|
||||
$from ?? Carbon::now()->subWeek(),
|
||||
$to ?? Carbon::now()
|
||||
))->map->format('Y-m-d');
|
||||
break;
|
||||
|
||||
case 'month':
|
||||
$period = collect(
|
||||
CarbonPeriod::create(
|
||||
$from ?? Carbon::now()->subMonth(),
|
||||
$to ?? Carbon::now()
|
||||
)
|
||||
)->map->format('Y-m-d');
|
||||
break;
|
||||
|
||||
case 'year':
|
||||
$period = collect(
|
||||
CarbonPeriod::create(
|
||||
$from ?? Carbon::now()->subYear(),
|
||||
$to ?? Carbon::now()
|
||||
)
|
||||
)->map->format('Y-m');
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($period as $moment) {
|
||||
$stats[$moment] = $data[$moment] ?? 0;
|
||||
}
|
||||
|
||||
return collect($stats);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,196 +0,0 @@
|
|||
<?php
|
||||
/*
|
||||
Flexisip Account Manager is a set of tools to manage SIP accounts.
|
||||
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Libraries;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Carbon\CarbonPeriod;
|
||||
use Carbon\CarbonInterval;
|
||||
|
||||
use App\Account;
|
||||
|
||||
class StatisticsCruncher
|
||||
{
|
||||
public static function month()
|
||||
{
|
||||
$data = self::getAccountFrom(Carbon::now()->subMonth())
|
||||
->get(array(
|
||||
DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
))->each->setAppends([])->pluck('count', 'moment');
|
||||
|
||||
$dataAliases = self::getAccountFrom(Carbon::now()->subMonth())
|
||||
->whereIn('id', function ($query) {
|
||||
$query->select('account_id')
|
||||
->from('aliases');
|
||||
})
|
||||
->get(array(
|
||||
DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
))->each->setAppends([])->pluck('count', 'moment');
|
||||
|
||||
$dataActivated = self::getAccountFrom(Carbon::now()->subMonth())
|
||||
->where('activated', true)
|
||||
->get(array(
|
||||
DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
))->each->setAppends([])->pluck('count', 'moment');
|
||||
|
||||
$dataAliasesActivated = self::getAccountFrom(Carbon::now()->subMonth())
|
||||
->where('activated', true)
|
||||
->whereIn('id', function ($query) {
|
||||
$query->select('account_id')
|
||||
->from('aliases');
|
||||
})
|
||||
->get(array(
|
||||
DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
))->each->setAppends([])->pluck('count', 'moment');
|
||||
|
||||
return self::compileStatistics(
|
||||
collect(CarbonPeriod::create(Carbon::now()->subMonth(), Carbon::now()))->map->format('Y-m-d'),
|
||||
$data,
|
||||
$dataAliases,
|
||||
$dataActivated,
|
||||
$dataAliasesActivated
|
||||
);
|
||||
}
|
||||
|
||||
public static function week()
|
||||
{
|
||||
$data = self::getAccountFrom(Carbon::now()->subWeek())
|
||||
->get(array(
|
||||
DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
))->each->setAppends([])->pluck('count', 'moment');
|
||||
|
||||
$dataAliases = self::getAccountFrom(Carbon::now()->subWeek())
|
||||
->whereIn('id', function ($query) {
|
||||
$query->select('account_id')
|
||||
->from('aliases');
|
||||
})
|
||||
->get(array(
|
||||
DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
))->each->setAppends([])->pluck('count', 'moment');
|
||||
|
||||
$dataActivated = self::getAccountFrom(Carbon::now()->subWeek())
|
||||
->where('activated', true)
|
||||
->get(array(
|
||||
DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
))->each->setAppends([])->pluck('count', 'moment');
|
||||
|
||||
$dataAliasesActivated = self::getAccountFrom(Carbon::now()->subWeek())
|
||||
->where('activated', true)
|
||||
->whereIn('id', function ($query) {
|
||||
$query->select('account_id')
|
||||
->from('aliases');
|
||||
})
|
||||
->get(array(
|
||||
DB::raw("date_format(created_at,'%Y-%m-%d') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
))->each->setAppends([])->pluck('count', 'moment');
|
||||
|
||||
return self::compileStatistics(
|
||||
collect(CarbonPeriod::create(Carbon::now()->subWeek(), Carbon::now()))->map->format('Y-m-d'),
|
||||
$data,
|
||||
$dataAliases,
|
||||
$dataActivated,
|
||||
$dataAliasesActivated
|
||||
);
|
||||
}
|
||||
|
||||
public static function day()
|
||||
{
|
||||
$data = self::getAccountFrom(Carbon::now()->subDay())
|
||||
->get(array(
|
||||
DB::raw("date_format(created_at,'%Y-%m-%d %H') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
))->each->setAppends([])->pluck('count', 'moment');
|
||||
|
||||
$dataAliases = self::getAccountFrom(Carbon::now()->subDay())
|
||||
->whereIn('id', function ($query) {
|
||||
$query->select('account_id')
|
||||
->from('aliases');
|
||||
})
|
||||
->get(array(
|
||||
DB::raw("date_format(created_at,'%Y-%m-%d %H') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
))->each->setAppends([])->pluck('count', 'moment');
|
||||
|
||||
$dataActivated = self::getAccountFrom(Carbon::now()->subDay())
|
||||
->where('activated', true)
|
||||
->get(array(
|
||||
DB::raw("date_format(created_at,'%Y-%m-%d %H') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
))->each->setAppends([])->pluck('count', 'moment');
|
||||
|
||||
$dataAliasesActivated = self::getAccountFrom(Carbon::now()->subDay())
|
||||
->where('activated', true)
|
||||
->whereIn('id', function ($query) {
|
||||
$query->select('account_id')
|
||||
->from('aliases');
|
||||
})
|
||||
->get(array(
|
||||
DB::raw("date_format(created_at,'%Y-%m-%d %H') as moment"),
|
||||
DB::raw('COUNT(*) as "count"')
|
||||
))->each->setAppends([])->pluck('count', 'moment');
|
||||
|
||||
return self::compileStatistics(
|
||||
collect(CarbonInterval::hour()->toPeriod(Carbon::now()->subDay(), Carbon::now()))->map->format('Y-m-d H'),
|
||||
$data,
|
||||
$dataAliases,
|
||||
$dataActivated,
|
||||
$dataAliasesActivated
|
||||
);
|
||||
}
|
||||
|
||||
private static function getAccountFrom($date)
|
||||
{
|
||||
return Account::where('created_at', '>=', $date)
|
||||
->groupBy('moment')
|
||||
->orderBy('moment', 'DESC')
|
||||
->setEagerLoads([]);
|
||||
}
|
||||
|
||||
private static function compileStatistics($period, $data, $dataAliases, $dataActivated, $dataAliasesActivated)
|
||||
{
|
||||
$stats = [];
|
||||
|
||||
foreach ($period as $moment) {
|
||||
$all = $data[$moment] ?? 0;
|
||||
$aliases = $dataAliases[$moment] ?? 0;
|
||||
$activated = $dataActivated[$moment] ?? 0;
|
||||
$activatedAliases = $dataAliasesActivated[$moment] ?? 0;
|
||||
|
||||
$stats[$moment] = [
|
||||
'all' => $all,
|
||||
'phone' => $aliases,
|
||||
'email' => $all - $aliases,
|
||||
'activated_phone' => $activatedAliases,
|
||||
'activated_email' => $activated - $activatedAliases
|
||||
];
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
}
|
||||
68
flexiapi/composer.lock
generated
68
flexiapi/composer.lock
generated
|
|
@ -399,16 +399,16 @@
|
|||
},
|
||||
{
|
||||
"name": "doctrine/dbal",
|
||||
"version": "3.6.4",
|
||||
"version": "3.6.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/dbal.git",
|
||||
"reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f"
|
||||
"reference": "96d5a70fd91efdcec81fc46316efc5bf3da17ddf"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f",
|
||||
"reference": "19f0dec95edd6a3c3c5ff1d188ea94c6b7fc903f",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/96d5a70fd91efdcec81fc46316efc5bf3da17ddf",
|
||||
"reference": "96d5a70fd91efdcec81fc46316efc5bf3da17ddf",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -423,10 +423,10 @@
|
|||
"require-dev": {
|
||||
"doctrine/coding-standard": "12.0.0",
|
||||
"fig/log-test": "^1",
|
||||
"jetbrains/phpstorm-stubs": "2022.3",
|
||||
"phpstan/phpstan": "1.10.14",
|
||||
"jetbrains/phpstorm-stubs": "2023.1",
|
||||
"phpstan/phpstan": "1.10.21",
|
||||
"phpstan/phpstan-strict-rules": "^1.5",
|
||||
"phpunit/phpunit": "9.6.7",
|
||||
"phpunit/phpunit": "9.6.9",
|
||||
"psalm/plugin-phpunit": "0.18.4",
|
||||
"squizlabs/php_codesniffer": "3.7.2",
|
||||
"symfony/cache": "^5.4|^6.0",
|
||||
|
|
@ -491,7 +491,7 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/dbal/issues",
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.6.4"
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.6.5"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -507,7 +507,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-06-15T07:40:12+00:00"
|
||||
"time": "2023-07-17T09:15:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
|
|
@ -1799,16 +1799,16 @@
|
|||
},
|
||||
{
|
||||
"name": "laravel/framework",
|
||||
"version": "v9.52.10",
|
||||
"version": "v9.52.12",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/framework.git",
|
||||
"reference": "858add225ce88a76c43aec0e7866288321ee0ee9"
|
||||
"reference": "8bfd22be79f437fa335e70692e4e91ff40ce561d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/858add225ce88a76c43aec0e7866288321ee0ee9",
|
||||
"reference": "858add225ce88a76c43aec0e7866288321ee0ee9",
|
||||
"url": "https://api.github.com/repos/laravel/framework/zipball/8bfd22be79f437fa335e70692e4e91ff40ce561d",
|
||||
"reference": "8bfd22be79f437fa335e70692e4e91ff40ce561d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -1993,20 +1993,20 @@
|
|||
"issues": "https://github.com/laravel/framework/issues",
|
||||
"source": "https://github.com/laravel/framework"
|
||||
},
|
||||
"time": "2023-06-27T13:25:54+00:00"
|
||||
"time": "2023-07-26T13:20:55+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/serializable-closure",
|
||||
"version": "v1.3.0",
|
||||
"version": "v1.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/laravel/serializable-closure.git",
|
||||
"reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37"
|
||||
"reference": "e5a3057a5591e1cfe8183034b0203921abe2c902"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f23fe9d4e95255dacee1bf3525e0810d1a1b0f37",
|
||||
"reference": "f23fe9d4e95255dacee1bf3525e0810d1a1b0f37",
|
||||
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/e5a3057a5591e1cfe8183034b0203921abe2c902",
|
||||
"reference": "e5a3057a5591e1cfe8183034b0203921abe2c902",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -2053,7 +2053,7 @@
|
|||
"issues": "https://github.com/laravel/serializable-closure/issues",
|
||||
"source": "https://github.com/laravel/serializable-closure"
|
||||
},
|
||||
"time": "2023-01-30T18:31:20+00:00"
|
||||
"time": "2023-07-14T13:56:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravelcollective/html",
|
||||
|
|
@ -3434,16 +3434,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "9.2.26",
|
||||
"version": "9.2.27",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||
"reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1"
|
||||
"reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
|
||||
"reference": "443bc6912c9bd5b409254a40f4b0f4ced7c80ea1",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/b0a88255cb70d52653d80c890bd7f38740ea50d1",
|
||||
"reference": "b0a88255cb70d52653d80c890bd7f38740ea50d1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -3499,7 +3499,8 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.26"
|
||||
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.27"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
|
@ -3507,7 +3508,7 @@
|
|||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2023-03-06T12:58:08+00:00"
|
||||
"time": "2023-07-26T13:44:30+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-file-iterator",
|
||||
|
|
@ -9009,16 +9010,16 @@
|
|||
},
|
||||
{
|
||||
"name": "mockery/mockery",
|
||||
"version": "1.6.3",
|
||||
"version": "1.6.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mockery/mockery.git",
|
||||
"reference": "b1be135c1ba7632f0248e07ee5e6e412576a309d"
|
||||
"reference": "d1413755e26fe56a63455f7753221c86cbb88f66"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mockery/mockery/zipball/b1be135c1ba7632f0248e07ee5e6e412576a309d",
|
||||
"reference": "b1be135c1ba7632f0248e07ee5e6e412576a309d",
|
||||
"url": "https://api.github.com/repos/mockery/mockery/zipball/d1413755e26fe56a63455f7753221c86cbb88f66",
|
||||
"reference": "d1413755e26fe56a63455f7753221c86cbb88f66",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
|
@ -9032,16 +9033,17 @@
|
|||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5 || ^9.3",
|
||||
"psalm/plugin-phpunit": "^0.18.4",
|
||||
"symplify/easy-coding-standard": "^11.5.0",
|
||||
"vimeo/psalm": "^5.13.1"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"src/helpers.php",
|
||||
"src/Mockery.php"
|
||||
"library/helpers.php",
|
||||
"library/Mockery.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Mockery\\": "src/Mockery"
|
||||
"Mockery\\": "library/Mockery"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
|
|
@ -9089,7 +9091,7 @@
|
|||
"security": "https://github.com/mockery/mockery/security/advisories",
|
||||
"source": "https://github.com/mockery/mockery"
|
||||
},
|
||||
"time": "2023-07-18T17:47:29+00:00"
|
||||
"time": "2023-07-19T15:51:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "nunomaduro/collision",
|
||||
|
|
|
|||
18
flexiapi/database/factories/StatisticsMessageFactory.php
Normal file
18
flexiapi/database/factories/StatisticsMessageFactory.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class StatisticsMessageFactory extends Factory
|
||||
{
|
||||
public function definition(): array
|
||||
{
|
||||
return [
|
||||
'id' => $this->faker->uuid(),
|
||||
'from' => $this->faker->email(),
|
||||
'sent_at' => $this->faker->dateTimeBetween('-1 year'),
|
||||
'encrypted' => false
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::disableForeignKeyConstraints();
|
||||
DB::query('delete from contacts_lists');
|
||||
Schema::enableForeignKeyConstraints();
|
||||
|
||||
Schema::table('contacts_lists', function (Blueprint $table) {
|
||||
$table->unique('title');
|
||||
$table->text('description')->nullable(true)->change();
|
||||
});
|
||||
|
||||
Schema::table('statistics_messages', function (Blueprint $table) {
|
||||
$table->index('sent_at');
|
||||
});
|
||||
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
$table->index('created_at');
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('contacts_lists', function (Blueprint $table) {
|
||||
$table->dropUnique('contacts_lists_title_unique');
|
||||
$table->text('description')->nullable(false)->change();
|
||||
});
|
||||
|
||||
Schema::table('statistics_messages', function (Blueprint $table) {
|
||||
$table->dropIndex('statistics_messages_sent_at_index');
|
||||
});
|
||||
|
||||
Schema::table('accounts', function (Blueprint $table) {
|
||||
$table->dropIndex('accounts_created_at_index');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
/*
|
||||
Flexisip Account Manager is a set of tools to manage SIP accounts.
|
||||
Copyright (C) 2021 Belledonne Communications SARL, All rights reserved.
|
||||
Copyright (C) 2020 Belledonne Communications SARL, All rights reserved.
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
|
|
@ -17,27 +17,25 @@
|
|||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
use App\Libraries\StatisticsCruncher;
|
||||
use App\StatisticsMessage;
|
||||
use App\StatisticsMessageDevice;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class StatisticController extends Controller
|
||||
class StatisticsMessagesSeeder extends Seeder
|
||||
{
|
||||
public function month(Request $request)
|
||||
public function run()
|
||||
{
|
||||
return StatisticsCruncher::month();
|
||||
}
|
||||
Schema::disableForeignKeyConstraints();
|
||||
StatisticsMessageDevice::truncate();
|
||||
StatisticsMessage::truncate();
|
||||
Schema::enableForeignKeyConstraints();
|
||||
|
||||
public function week(Request $request)
|
||||
{
|
||||
return StatisticsCruncher::week();
|
||||
}
|
||||
|
||||
public function day(Request $request)
|
||||
{
|
||||
return StatisticsCruncher::day();
|
||||
StatisticsMessage::factory()
|
||||
->count(10000)
|
||||
->create();
|
||||
}
|
||||
}
|
||||
8
flexiapi/public/css/far.css
vendored
8
flexiapi/public/css/far.css
vendored
|
|
@ -637,6 +637,10 @@ table tr.empty td:before {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.chip.selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/** Pagination **/
|
||||
|
||||
ul.pagination {
|
||||
|
|
@ -704,3 +708,7 @@ select.list_toggle {
|
|||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#chart {
|
||||
min-height: 80vh;
|
||||
}
|
||||
|
|
@ -114,4 +114,4 @@ An adminisator can create, edit and delete account types. Those can be used to c
|
|||
|
||||
## Statistics
|
||||
|
||||
The statistics panel show registrations statistics based on their type (mobile and email based registration) and their activations states.
|
||||
The statistics panel show different statistics recorder by the Account Manager, they can be explored, filtered and exported.
|
||||
|
|
@ -1,74 +1,71 @@
|
|||
@extends('layouts.main', ['welcome' => true])
|
||||
|
||||
@section('content')
|
||||
<section>
|
||||
<p class="oppose">
|
||||
You already have an account?
|
||||
<a class="btn btn-secondary" href="{{ route('account.login') }}">Login</a>
|
||||
</p>
|
||||
|
||||
<section>
|
||||
<h1><i class="material-icons">account_circle</i> Register</h1>
|
||||
|
||||
<p class="oppose">
|
||||
You already have an account?
|
||||
<a class="btn btn-secondary" href="{{ route('account.login') }}">Login</a>
|
||||
</p>
|
||||
@include('parts.tabs.register')
|
||||
|
||||
<h1><i class="material-icons">account_circle</i> Register</h1>
|
||||
{!! Form::open(['route' => 'account.store']) !!}
|
||||
|
||||
@include('parts.tabs.register')
|
||||
<div>
|
||||
{!! Form::text('username', old('username'), ['placeholder' => 'username', 'required']) !!}
|
||||
{!! Form::label('username', 'Username') !!}
|
||||
@include('parts.errors', ['name' => 'username'])
|
||||
</div>
|
||||
|
||||
{!! Form::open(['route' => 'account.store']) !!}
|
||||
<div>
|
||||
<input type="text" name="username" value="{{ $domain }}" disabled>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{!! Form::text('username', old('username'), ['placeholder' => 'username', 'required']) !!}
|
||||
{!! Form::label('username', 'Username') !!}
|
||||
@include('parts.errors', ['name' => 'username'])
|
||||
</div>
|
||||
<div>
|
||||
{!! Form::email('email', old('email'), ['placeholder' => 'bob@example.net', 'required']) !!}
|
||||
{!! Form::label('email', 'Email') !!}
|
||||
@include('parts.errors', ['name' => 'email'])
|
||||
</div>
|
||||
<div>
|
||||
{!! Form::email('email_confirmation', old('email_confirm'), ['placeholder' => 'bob@example.net', 'required']) !!}
|
||||
{!! Form::label('email_confirmation', 'Confirm email') !!}
|
||||
@include('parts.errors', ['name' => 'email_confirmation'])
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input type="text" name="username" value="{{ $domain }}" disabled>
|
||||
</div>
|
||||
<div>
|
||||
<input required="" name="password" type="password" value="" placeholder="Password">
|
||||
<label for="password">Password</label>
|
||||
@include('parts.errors', ['name' => 'password'])
|
||||
</div>
|
||||
<div>
|
||||
<input required="" name="password_confirmation" type="password" value="" placeholder="Password confirmation">
|
||||
<label for="password_confirmation">Confirm password</label>
|
||||
@include('parts.errors', ['name' => 'password_confirmation'])
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{!! Form::email('email', old('email'), ['placeholder' => 'bob@example.net', 'required']) !!}
|
||||
{!! Form::label('email', 'Email') !!}
|
||||
@include('parts.errors', ['name' => 'email'])
|
||||
</div>
|
||||
<div>
|
||||
{!! Form::email('email_confirmation', old('email_confirm'), ['placeholder' => 'bob@example.net', 'required']) !!}
|
||||
{!! Form::label('email_confirmation', 'Confirm email') !!}
|
||||
@include('parts.errors', ['name' => 'email_confirmation'])
|
||||
</div>
|
||||
@if (!empty(config('app.newsletter_registration_address')))
|
||||
<div class="large checkbox">
|
||||
{!! Form::checkbox('newsletter', 'true', false, ['class' => 'form-check-input', 'id' => 'newsletter']) !!}
|
||||
<label class="form-check-label" for="newsletter">I would like to subscribe to the newsletter</a></label>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div>
|
||||
{!! Form::password('password', ['required']) !!}
|
||||
{!! Form::label('password', 'Password') !!}
|
||||
@include('parts.errors', ['name' => 'password'])
|
||||
</div>
|
||||
<div>
|
||||
{!! Form::password('password_confirmation', ['required']) !!}
|
||||
{!! Form::label('password_confirmation', 'Confirm password') !!}
|
||||
@include('parts.errors', ['name' => 'password_confirmation'])
|
||||
</div>
|
||||
@include('parts.terms')
|
||||
|
||||
@if (!empty(config('app.newsletter_registration_address')))
|
||||
<div class="large checkbox">
|
||||
{!! Form::checkbox('newsletter', 'true', false, ['class' => 'form-check-input', 'id' => 'newsletter']) !!}
|
||||
<label class="form-check-label" for="newsletter">I would like to subscribe to the newsletter</a></label>
|
||||
</div>
|
||||
@endif
|
||||
<div class="large">
|
||||
{!! Form::submit('Register', ['class' => 'btn oppose']) !!}
|
||||
</div>
|
||||
|
||||
@include('parts.terms')
|
||||
|
||||
<div class="large">
|
||||
{!! Form::submit('Register', ['class' => 'btn oppose']) !!}
|
||||
</div>
|
||||
|
||||
{!! Form::close() !!}
|
||||
|
||||
</section>
|
||||
<section class="on_desktop">
|
||||
<img src="/img/login.svg">
|
||||
</section>
|
||||
{!! Form::close() !!}
|
||||
|
||||
</section>
|
||||
<section class="on_desktop">
|
||||
<img src="/img/login.svg">
|
||||
</section>
|
||||
@endsection
|
||||
|
||||
@section('footer')
|
||||
Hop
|
||||
Hop
|
||||
@endsection
|
||||
|
|
@ -31,13 +31,14 @@
|
|||
<div></div>
|
||||
|
||||
<div>
|
||||
{!! Form::password('password', ['required']) !!}
|
||||
{!! Form::label('password', 'Password') !!}
|
||||
<input required="" name="password" type="password" value="" placeholder="Password">
|
||||
<label for="password">Password</label>
|
||||
@include('parts.errors', ['name' => 'password'])
|
||||
</div>
|
||||
<div>
|
||||
{!! Form::password('password_confirmation', ['required']) !!}
|
||||
{!! Form::label('password_confirmation', 'Confirm password') !!}
|
||||
<input required="" name="password_confirmation" type="password" value="" placeholder="Password confirmation">
|
||||
<label for="password_confirmation">Confirm password</label>
|
||||
@include('parts.errors', ['name' => 'password_confirmation'])
|
||||
</div>
|
||||
|
||||
@include('parts.terms')
|
||||
|
|
|
|||
|
|
@ -1,18 +1,24 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
<div>
|
||||
@if ($account->id)
|
||||
<a class="btn oppose btn-secondary" href="{{ route('admin.account.delete', $account->id) }}">
|
||||
@if ($account->id)
|
||||
<header>
|
||||
<h1><i class="material-icons">people</i> Edit an account</h1>
|
||||
<a href="{{ route('admin.account.index') }}" class="btn btn-secondary oppose">Cancel</a>
|
||||
<a class="btn btn-secondary" href="{{ route('admin.account.delete', $account->id) }}">
|
||||
<i class="material-icons">delete</i>
|
||||
Delete
|
||||
</a>
|
||||
<h1><i class="material-icons">people</i> Edit an account</h1>
|
||||
<p title="{{ $account->updated_at }}">Updated on {{ $account->updated_at->format('d/m/Y') }}
|
||||
@else
|
||||
<input form="create_edit" class="btn" type="submit" value="Update">
|
||||
</header>
|
||||
<p title="{{ $account->updated_at }}">Updated on {{ $account->updated_at->format('d/m/Y') }}
|
||||
@else
|
||||
<header>
|
||||
<h1><i class="material-icons">people</i> Create an account</h1>
|
||||
@endif
|
||||
</div>
|
||||
<a href="{{ route('admin.account.index') }}" class="btn btn-secondary oppose">Cancel</a>
|
||||
<input form="create_edit" class="btn" type="submit" value="Create">
|
||||
</header>
|
||||
@endif
|
||||
|
||||
<form method="POST"
|
||||
action="{{ $account->id ? route('admin.account.update', $account->id) : route('admin.account.store') }}"
|
||||
|
|
@ -95,12 +101,6 @@
|
|||
|
||||
</form>
|
||||
|
||||
<form>
|
||||
<div class="large">
|
||||
<input class="btn oppose" type="submit" value="{{ $account->id ? 'Update' : 'Create' }}" form="create_edit">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<hr class="large">
|
||||
|
||||
@if ($account->id)
|
||||
|
|
|
|||
|
|
@ -4,13 +4,16 @@
|
|||
<header>
|
||||
@if ($contacts_list->id)
|
||||
<h1><i class="material-icons">account_box</i> Edit a Contacts List</h1>
|
||||
<a class="btn oppose btn-secondary" href="{{ route('admin.contacts_lists.delete', $contacts_list->id) }}">
|
||||
<a href="{{ route('admin.contacts_lists.index') }}" class="btn btn-secondary oppose">Cancel</a>
|
||||
<a class="btn btn-secondary" href="{{ route('admin.contacts_lists.delete', $contacts_list->id) }}">
|
||||
<i class="material-icons">delete</i>
|
||||
Delete
|
||||
</a>
|
||||
<input form="create_edit_contacts_list" class="btn" type="submit" value="{{ $contacts_list->id ? 'Update' : 'Create' }}">
|
||||
<input form="create_edit_contacts_list" class="btn" type="submit" value="Update">
|
||||
@else
|
||||
<h1><i class="material-icons">account_box</i> Create a Contacts List</h1>
|
||||
<a href="{{ route('admin.contacts_lists.index') }}" class="btn btn-secondary oppose">Cancel</a>
|
||||
<input form="create_edit_contacts_list" class="btn" type="submit" value="Create">
|
||||
@endif
|
||||
</header>
|
||||
|
||||
|
|
@ -18,20 +21,19 @@
|
|||
<p title="{{ $contacts_list->updated_at }}">Updated on {{ $contacts_list->updated_at->format('d/m/Y') }}
|
||||
@endif
|
||||
|
||||
<form method="POST"
|
||||
id="create_edit_contacts_list"
|
||||
<form method="POST" id="create_edit_contacts_list"
|
||||
action="{{ $contacts_list->id ? route('admin.contacts_lists.update', $contacts_list->id) : route('admin.contacts_lists.store') }}"
|
||||
accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method($contacts_list->id ? 'put' : 'post')
|
||||
<div>
|
||||
<input placeholder="Name" required="required" name="title" type="text" value="{{ $contacts_list->title }}">
|
||||
<input placeholder="Name" required="required" name="title" type="text" value="{{ $contacts_list->title ?? old('title') }}">
|
||||
<label for="username">Name</label>
|
||||
@include('parts.errors', ['name' => 'title'])
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<textarea placeholder="Description" required="required" name="description">{{ $contacts_list->description }}</textarea>
|
||||
<textarea placeholder="Description" name="description">{{ $contacts_list->description ?? old('description') }}</textarea>
|
||||
<label for="description">Description</label>
|
||||
@include('parts.errors', ['name' => 'description'])
|
||||
</div>
|
||||
|
|
@ -45,15 +47,15 @@
|
|||
<span class="list_toggle" data-list-id="d{{ $contacts_list->id }}"></span> selected
|
||||
</p>
|
||||
|
||||
<form method="POST"
|
||||
action="{{ route('admin.contacts_lists.contacts.destroy', $contacts_list->id) }}"
|
||||
accept-charset="UTF-8">
|
||||
<form method="POST" action="{{ route('admin.contacts_lists.contacts.destroy', $contacts_list->id) }}"
|
||||
accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('delete')
|
||||
|
||||
<select name="contacts_ids[]" class="list_toggle" data-list-id="d{{ $contacts_list->id }}"></select>
|
||||
<input type="hidden" name="contacts_list_id" value="{{ $contacts_list->id }}">
|
||||
<input class="btn btn-tertiary" type="submit" value="Remove contacts" onclick="Utils.clearStorageList('d{{ $contacts_list->id }}')">
|
||||
<input class="btn btn-tertiary" type="submit" value="Remove contacts"
|
||||
onclick="Utils.clearStorageList('d{{ $contacts_list->id }}')">
|
||||
</form>
|
||||
|
||||
<a class="btn btn-secondary" href="{{ route('admin.contacts_lists.contacts.add', $contacts_list->id) }}">
|
||||
|
|
@ -79,7 +81,8 @@
|
|||
@foreach ($contacts_list->contacts as $contact)
|
||||
<tr>
|
||||
<td>
|
||||
<input class="list_toggle" type="checkbox" data-list-id="d{{ $contacts_list->id }}" data-id="{{ $contact->id }}">
|
||||
<input class="list_toggle" type="checkbox" data-list-id="d{{ $contacts_list->id }}"
|
||||
data-id="{{ $contact->id }}">
|
||||
</td>
|
||||
<td>{{ $contact->identifier }}</td>
|
||||
</tr>
|
||||
|
|
|
|||
47
flexiapi/resources/views/admin/statistics/show.blade.php
Normal file
47
flexiapi/resources/views/admin/statistics/show.blade.php
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
@include('parts.tabs', [
|
||||
'items' => [
|
||||
route('admin.statistics.show', ['type' => 'messages']) => 'Messages',
|
||||
route('admin.statistics.show', ['type' => 'accounts']) => 'Accounts',
|
||||
],
|
||||
])
|
||||
|
||||
<header>
|
||||
<form class="inline" method="POST" action="{{ route('admin.statistics.edit') }}" accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('post')
|
||||
|
||||
<input type="hidden" name="by" value="{{ $by }}">
|
||||
<input type="hidden" name="type" value="{{ $type }}">
|
||||
|
||||
<div>
|
||||
<input type="date" name="from" value="{{ $request->get('from') }}" onchange="this.form.submit()">
|
||||
<label for="from">From</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="date" name="to" value="{{ $request->get('to') }}" onchange="this.form.submit()">
|
||||
<label for="to">To</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<a href="{{ route('admin.statistics.show', ['by' => 'day', 'type' => $type] + $request->only(['from', 'to'])) }}"
|
||||
class="chip @if ($by == 'day') selected @endif">Day</a>
|
||||
<a href="{{ route('admin.statistics.show', ['by' => 'week', 'type' => $type] + $request->only(['from', 'to'])) }}"
|
||||
class="chip @if ($by == 'week') selected @endif">Week</a>
|
||||
<a href="{{ route('admin.statistics.show', ['by' => 'month', 'type' => $type] + $request->only(['from', 'to'])) }}"
|
||||
class="chip @if ($by == 'month') selected @endif">Month</a>
|
||||
<a href="{{ route('admin.statistics.show', ['by' => 'year', 'type' => $type] + $request->only(['from', 'to'])) }}"
|
||||
class="chip @if ($by == 'year') selected @endif">Year</a>
|
||||
</div>
|
||||
|
||||
<a class="btn btn-secondary" href="{{ route('admin.statistics.show') }}">Reset</a>
|
||||
<a class="btn btn-tertiary" href="{{ route('admin.statistics.show', ['by' => $by, 'type' => $type, 'export' => true] + $request->only(['from', 'to'])) }}">
|
||||
<i class="material-icons">download</i> Export
|
||||
</a>
|
||||
</form>
|
||||
</header>
|
||||
|
||||
@include('parts.graph')
|
||||
@endsection
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
Statistics
|
||||
</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
|
||||
<ul class="nav justify-content-center">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link disabled" href="#">Day</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('admin.statistics.show.week') }}">Week</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('admin.statistics.show.month') }}">Month</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>Statistics</h2>
|
||||
|
||||
@include('admin.statistics.parts.legend')
|
||||
|
||||
<h3>Day</h3>
|
||||
|
||||
<div class="columns">
|
||||
@foreach ($day as $key => $hour)
|
||||
<div class="column" data-value="{{ substr($key, -2, 2) }}:00">
|
||||
@include('admin.statistics.parts.columns', ['slice' => $hour, 'max' => $max_day])
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
Statistics
|
||||
</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
|
||||
<ul class="nav justify-content-center">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('admin.statistics.show.day') }}">Day</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('admin.statistics.show.week') }}">Week</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link disabled" href="#">Month</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>Statistics</h2>
|
||||
|
||||
@include('admin.statistics.parts.legend')
|
||||
|
||||
<h3>Month</h3>
|
||||
|
||||
<div class="columns">
|
||||
@foreach ($month as $key => $day)
|
||||
<div class="column" data-value="{{ $key }}">
|
||||
@include('admin.statistics.parts.columns', ['slice' => $day, 'max' => $max_month])
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
Statistics
|
||||
</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
|
||||
<ul class="nav justify-content-center">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('admin.statistics.show.day') }}">Day</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link disabled" href="#">Week</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="{{ route('admin.statistics.show.month') }}">Month</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h2>Statistics</h2>
|
||||
|
||||
@include('admin.statistics.parts.legend')
|
||||
|
||||
<h3>Week</h3>
|
||||
|
||||
<div class="columns">
|
||||
@foreach ($week as $key => $day)
|
||||
<div class="column" data-value="{{ $key }}">
|
||||
@include('admin.statistics.parts.columns', ['slice' => $day, 'max' => $max_week])
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
|
@ -525,20 +525,6 @@ JSON parameters:
|
|||
* `to` required, SIP address of the receiver
|
||||
* `body` required, content of the message
|
||||
|
||||
## Statistics
|
||||
|
||||
### `GET /statistics/day`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
Retrieve registrations statistics for 24 hours.
|
||||
|
||||
### `GET /statistics/week`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
Retrieve registrations statistics for a week.
|
||||
|
||||
### `GET /statistics/month`
|
||||
<span class="badge badge-warning">Admin</span>
|
||||
Retrieve registrations statistics for a month.
|
||||
|
||||
# Non-API Endpoints
|
||||
|
||||
The following URLs are **not API endpoints** they are not returning `JSON` content and they are not located under `/api` but directly under the root path.
|
||||
|
|
|
|||
|
|
@ -12,6 +12,9 @@
|
|||
<link rel="stylesheet" type="text/css" href="{{ asset('css/far.css') }}">
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('css/form.css') }}">
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>
|
||||
|
||||
@if (config('instance.custom_theme') & file_exists(public_path('css/' . config('app.env') . '.style.css')))
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('css/' . config('app.env') . '.style.css') }}">
|
||||
<!--<link rel="stylesheet" type="text/css" href="{{ asset('css/charts.css') }}" >-->
|
||||
|
|
|
|||
27
flexiapi/resources/views/parts/graph.blade.php
Normal file
27
flexiapi/resources/views/parts/graph.blade.php
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
<div id="chart" @if (isset($style))style="{{ $style }}" @endif></div>
|
||||
|
||||
<script>
|
||||
const config = {!! $jsonConfig !!};
|
||||
|
||||
chart = document.getElementById('chart');
|
||||
chart.innerHTML = '';
|
||||
|
||||
canvas = document.createElement('canvas');
|
||||
canvas.id = 'myChart';
|
||||
chart.appendChild(canvas);
|
||||
|
||||
@if (isset($withDataLabel) && $withDataLabel)
|
||||
config.plugins = [ChartDataLabels];
|
||||
@endif
|
||||
|
||||
@if (isset($showLabel) && $showLabel)
|
||||
config.options.plugins.datalabels.formatter = function(value, context) {
|
||||
return value + ' ' + context.dataset.label;
|
||||
}
|
||||
@endif
|
||||
|
||||
new Chart(
|
||||
document.getElementById('myChart'),
|
||||
config
|
||||
);
|
||||
</script>
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
if (auth()->user() && auth()->user()->admin) {
|
||||
$items['admin.account.index'] = ['title' => 'Accounts', 'icon' => 'people'];
|
||||
$items['admin.contacts_lists.index'] = ['title' => 'Contacts Lists', 'icon' => 'account_box'];
|
||||
$items['admin.statistics.show.day'] = ['title' => 'Statistics', 'icon' => 'analytics'];
|
||||
$items['admin.statistics.show'] = ['title' => 'Statistics', 'icon' => 'analytics'];
|
||||
}
|
||||
@endphp
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<ul class="tabs">
|
||||
@foreach ($items as $route => $title)
|
||||
<li @if (url()->current() == route($route))class="current"@endif><a href="{{ route($route) }}">{{ $title }}</a></li>
|
||||
<li @if (url()->current() == $route)class="current"@endif><a href="{{ $route }}">{{ $title }}</a></li>
|
||||
@endforeach
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
@if(config('app.phone_authentication'))
|
||||
@include('parts.tabs', ['items' => [
|
||||
'account.register.email' => 'Email registration',
|
||||
'account.register.phone' => 'Phone registration',
|
||||
route('account.register.phone') => 'Phone registration',
|
||||
route('account.register.email') => 'Email registration',
|
||||
]])
|
||||
@endif
|
||||
|
|
|
|||
|
|
@ -54,10 +54,6 @@ Route::post('accounts/auth_token', 'Api\Account\AuthTokenController@store');
|
|||
Route::get('accounts/me/api_key/{auth_token}', 'Api\Account\ApiKeyController@generateFromToken')->middleware('cookie', 'cookie.encrypt');
|
||||
|
||||
Route::group(['middleware' => ['auth.digest_or_key']], function () {
|
||||
Route::get('statistic/month', 'Api\StatisticController@month');
|
||||
Route::get('statistic/week', 'Api\StatisticController@week');
|
||||
Route::get('statistic/day', 'Api\StatisticController@day');
|
||||
|
||||
Route::get('accounts/auth_token/{auth_token}/attach', 'Api\Account\AuthTokenController@attach');
|
||||
|
||||
Route::get('accounts/me/api_key', 'Api\Account\ApiKeyController@generate')->middleware('cookie', 'cookie.encrypt');
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ use App\Http\Controllers\Admin\AccountTypeController;
|
|||
use App\Http\Controllers\Admin\AccountController as AdminAccountController;
|
||||
use App\Http\Controllers\Admin\ContactsListController;
|
||||
use App\Http\Controllers\Admin\ContactsListContactController;
|
||||
|
||||
use App\Http\Controllers\Admin\StatisticsController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::redirect('/', '/login')->name('account.home');
|
||||
|
|
@ -131,11 +131,11 @@ if (config('app.web_panel')) {
|
|||
Route::get('auth_tokens/auth/{token}', 'Account\AuthTokenController@auth')->name('auth_tokens.auth');
|
||||
|
||||
Route::name('admin.')->prefix('admin')->middleware(['auth.admin'])->group(function () {
|
||||
|
||||
// Statistics
|
||||
Route::get('statistics/day', 'Admin\StatisticsController@showDay')->name('statistics.show.day');
|
||||
Route::get('statistics/week', 'Admin\StatisticsController@showWeek')->name('statistics.show.week');
|
||||
Route::get('statistics/month', 'Admin\StatisticsController@showMonth')->name('statistics.show.month');
|
||||
Route::name('statistics.')->controller(StatisticsController::class)->prefix('statistics')->group(function () {
|
||||
Route::get('/', 'index')->name('index');
|
||||
Route::get('/{type?}', 'show')->name('show');
|
||||
Route::post('/', 'edit')->name('edit');
|
||||
});
|
||||
|
||||
Route::name('account.')->prefix('accounts')->group(function () {
|
||||
Route::controller(AdminAccountController::class)->group(function () {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue