mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-05-03 12:56:19 +00:00
Add ContactList feature, complete the lists and attach them to accounts
This commit is contained in:
parent
806a77a756
commit
c1e355a829
35 changed files with 1200 additions and 409 deletions
|
|
@ -132,6 +132,11 @@ class Account extends Authenticatable
|
|||
return $this->belongsToMany(Account::class, 'contacts', 'account_id', 'contact_id');
|
||||
}
|
||||
|
||||
public function contactsLists()
|
||||
{
|
||||
return $this->belongsToMany(ContactsList::class, 'account_contacts_list', 'account_id', 'contacts_list_id');
|
||||
}
|
||||
|
||||
public function nonces()
|
||||
{
|
||||
return $this->hasMany(DigestNonce::class);
|
||||
|
|
|
|||
18
flexiapi/app/ContactsList.php
Normal file
18
flexiapi/app/ContactsList.php
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace App;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ContactsList extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $withCount = ['contacts'];
|
||||
|
||||
public function contacts()
|
||||
{
|
||||
return $this->belongsToMany(Account::class, 'contacts_list_contact', 'contacts_list_id', 'contact_id');
|
||||
}
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ use Illuminate\Support\Facades\Schema;
|
|||
use League\CommonMark\CommonMarkConverter;
|
||||
use League\CommonMark\Extension\HeadingPermalink\HeadingPermalinkExtension;
|
||||
use League\CommonMark\Extension\TableOfContents\TableOfContentsExtension;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
function generateNonce(): string
|
||||
{
|
||||
|
|
@ -130,3 +131,23 @@ function resolveDomain(Request $request): string
|
|||
? $request->get('domain')
|
||||
: config('app.sip_domain');
|
||||
}
|
||||
|
||||
function resolveUserContacts(Request $request)
|
||||
{
|
||||
$selected = ['id', 'username', 'domain', 'activated', 'dtmf_protocol'];
|
||||
|
||||
return Account::whereIn('id', function ($query) use ($request) {
|
||||
$query->select('contact_id')
|
||||
->from('contacts')
|
||||
->where('account_id', $request->user()->id)
|
||||
->union(
|
||||
DB::table('contacts_list_contact')
|
||||
->select('contact_id')
|
||||
->whereIn('contacts_list_id', function ($query) use ($request) {
|
||||
$query->select('contacts_list_id')
|
||||
->from('account_contacts_list')
|
||||
->where('account_id', $request->user()->id);
|
||||
})
|
||||
);
|
||||
})->select($selected);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ class ContactVcardController extends Controller
|
|||
public function index(Request $request)
|
||||
{
|
||||
return response(
|
||||
$request->user()->contacts->map(function ($contact) {
|
||||
resolveUserContacts($request)->get()->map(function ($contact) {
|
||||
return $contact->toVcard4();
|
||||
})->implode("\n")
|
||||
);
|
||||
|
|
@ -36,8 +36,7 @@ class ContactVcardController extends Controller
|
|||
|
||||
public function show(Request $request, string $sip)
|
||||
{
|
||||
return $request->user()
|
||||
->contacts()
|
||||
return resolveUserContacts($request)
|
||||
->sip($sip)
|
||||
->firstOrFail()
|
||||
->toVcard4();
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class AccountAccountTypeController extends Controller
|
|||
$request->session()->flash('success', 'Type successfully added');
|
||||
Log::channel('events')->info('Web Admin: Account type attached', ['id' => $account->identifier, 'type_id' => $request->get('account_type_id')]);
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
return redirect()->route('admin.account.edit', $account);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $id, int $typeId)
|
||||
|
|
@ -68,6 +68,6 @@ class AccountAccountTypeController extends Controller
|
|||
$request->session()->flash('success', 'Type successfully removed');
|
||||
Log::channel('events')->info('Web Admin: Account type detached', ['id' => $account->identifier, 'type_id' => $request->get('account_type_id')]);
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
return redirect()->route('admin.account.edit', $account);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class AccountActionController extends Controller
|
|||
$request->session()->flash('success', 'Action successfully created');
|
||||
Log::channel('events')->info('Web Admin: Account action created', ['id' => $account->identifier, 'action' => $accountAction->key]);
|
||||
|
||||
return redirect()->route('admin.account.show', $accountAction->account);
|
||||
return redirect()->route('admin.account.edit', $accountAction->account);
|
||||
}
|
||||
|
||||
public function edit(int $id, int $actionId)
|
||||
|
|
@ -93,7 +93,7 @@ class AccountActionController extends Controller
|
|||
$request->session()->flash('success', 'Action successfully updated');
|
||||
Log::channel('events')->info('Web Admin: Account action updated', ['id' => $account->identifier, 'action' => $accountAction->key]);
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
return redirect()->route('admin.account.edit', $account);
|
||||
}
|
||||
|
||||
public function delete(int $id, int $actionId)
|
||||
|
|
@ -120,6 +120,6 @@ class AccountActionController extends Controller
|
|||
|
||||
Log::channel('events')->info('Web Admin: Account action deleted', ['id' => $accountAction->account->identifier, 'action_id' => $accountAction->key]);
|
||||
|
||||
return redirect()->route('admin.account.show', $accountAction->account);
|
||||
return redirect()->route('admin.account.edit', $accountAction->account);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class AccountContactController extends Controller
|
|||
|
||||
Log::channel('events')->info('Web Admin: Account contact added', ['id' => $account->identifier, 'contact' => $contact->identifier]);
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
return redirect()->route('admin.account.edit', $account);
|
||||
}
|
||||
|
||||
public function delete(int $id, int $contactId)
|
||||
|
|
@ -78,6 +78,6 @@ class AccountContactController extends Controller
|
|||
$request->session()->flash('success', 'Type successfully removed');
|
||||
Log::channel('events')->info('Web Admin: Account contact removed', ['id' => $account->identifier, 'contact' => $contact->identifier]);
|
||||
|
||||
return redirect()->route('admin.account.show', $account);
|
||||
return redirect()->route('admin.account.edit', $account);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ use Illuminate\Support\Facades\Log;
|
|||
use Carbon\Carbon;
|
||||
|
||||
use App\Account;
|
||||
use App\Admin;
|
||||
use App\ContactsList;
|
||||
use App\ExternalAccount;
|
||||
use App\Http\Requests\CreateAccountRequest;
|
||||
use App\Http\Requests\UpdateAccountRequest;
|
||||
|
|
@ -58,14 +58,6 @@ class AccountController extends Controller
|
|||
return redirect()->route('admin.account.index', $request->except('_token'));
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
{
|
||||
return view('admin.account.show', [
|
||||
'external_accounts_count' => ExternalAccount::where('used', false)->count(),
|
||||
'account' => Account::findOrFail($id)
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
return view('admin.account.create_edit', [
|
||||
|
|
@ -93,14 +85,20 @@ class AccountController extends Controller
|
|||
|
||||
Log::channel('events')->info('Web Admin: Account created', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->route('admin.account.show', $account->id);
|
||||
return redirect()->route('admin.account.edit', $account->id);
|
||||
}
|
||||
|
||||
public function edit(int $id)
|
||||
{
|
||||
return view('admin.account.create_edit', [
|
||||
'account' => Account::findOrFail($id),
|
||||
'protocols' => [null => 'None'] + Account::$dtmfProtocols
|
||||
'protocols' => [null => 'None'] + Account::$dtmfProtocols,
|
||||
'external_accounts_count' => ExternalAccount::where('used', false)->count(),
|
||||
'contacts_lists' => ContactsList::whereNotIn('id', function ($query) use ($id) {
|
||||
$query->select('contacts_list_id')
|
||||
->from('account_contacts_list')
|
||||
->where('account_id', $id);
|
||||
})->get()
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
@ -125,7 +123,7 @@ class AccountController extends Controller
|
|||
|
||||
Log::channel('events')->info('Web Admin: Account updated', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->route('admin.account.show', $id);
|
||||
return redirect()->route('admin.account.edit', $id);
|
||||
}
|
||||
|
||||
public function attachExternalAccount(int $id)
|
||||
|
|
@ -146,7 +144,7 @@ class AccountController extends Controller
|
|||
|
||||
Log::channel('events')->info('Web Admin: Account provisioned', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->back();
|
||||
return redirect()->back()->withFragment('provisioning');
|
||||
}
|
||||
|
||||
public function delete(int $id)
|
||||
|
|
@ -169,4 +167,25 @@ class AccountController extends Controller
|
|||
|
||||
return redirect()->route('admin.account.index');
|
||||
}
|
||||
|
||||
public function attachContactsList(Request $request, int $id)
|
||||
{
|
||||
$request->validate([
|
||||
'contacts_list_id' => 'required|exists:contacts_lists,id'
|
||||
]);
|
||||
|
||||
$account = Account::findOrFail($id);
|
||||
$account->contactsLists()->detach([$request->get('contacts_list_id')]);
|
||||
$account->contactsLists()->attach([$request->get('contacts_list_id')]);
|
||||
|
||||
return redirect()->route('admin.account.edit', $id);
|
||||
}
|
||||
|
||||
public function detachContactsList(Request $request, int $id)
|
||||
{
|
||||
$account = Account::findOrFail($id);
|
||||
$account->contactsLists()->detach([$request->get('contacts_list_id')]);
|
||||
|
||||
return redirect()->route('admin.account.edit', $id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
/*
|
||||
Flexisip Account Manager is a set of tools to manage SIP accounts.
|
||||
Copyright (C) 2023 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\Http\Controllers\Admin;
|
||||
|
||||
use App\Account;
|
||||
use App\ContactsList;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ContactsListContactController extends Controller
|
||||
{
|
||||
public function add(Request $request, int $contactsListId)
|
||||
{
|
||||
$accounts = Account::orderBy('updated_at', $request->get('updated_at_order', 'desc'))
|
||||
->with('externalAccount');
|
||||
|
||||
if ($request->has('search')) {
|
||||
$accounts = $accounts->where('username', 'like', '%' . $request->get('search') . '%');
|
||||
}
|
||||
|
||||
return view('admin.contacts_list.contacts.add', [
|
||||
'contacts_list' => ContactsList::firstOrFail($contactsListId),
|
||||
'params' => [
|
||||
'search' => $request->get('search'),
|
||||
'contacts_list_id' => $contactsListId,
|
||||
'updated_at_order' => $request->get('updated_at_order') == 'desc' ? 'asc' : 'desc'
|
||||
],
|
||||
'accounts' => $accounts->whereNotIn('id', function ($query) use ($contactsListId) {
|
||||
$query->select('contact_id')
|
||||
->from('contacts_list_contact')
|
||||
->where('contacts_list_id', $contactsListId);
|
||||
})->paginate(20)->appends($request->query()),
|
||||
]);
|
||||
}
|
||||
|
||||
public function search(Request $request, int $contactsListId)
|
||||
{
|
||||
return redirect()->route('admin.contacts_lists.contacts.add', ['contacts_list_id' => $contactsListId] + $request->except('_token'));
|
||||
}
|
||||
|
||||
public function store(Request $request, int $contactsListId)
|
||||
{
|
||||
$request->validate([
|
||||
'contacts_ids' => 'required|exists:accounts,id'
|
||||
]);
|
||||
|
||||
$contactsList = ContactsList::firstOrFail($contactsListId);
|
||||
$contactsList->contacts()->detach($request->get('contacts_ids')); // Just in case
|
||||
$contactsList->contacts()->attach($request->get('contacts_ids'));
|
||||
|
||||
return redirect()->route('admin.contacts_lists.edit', $contactsList->id);
|
||||
}
|
||||
|
||||
public function destroy(Request $request, int $contactsListId)
|
||||
{
|
||||
$request->validate([
|
||||
'contacts_ids' => 'required|exists:accounts,id'
|
||||
]);
|
||||
|
||||
$contactsList = ContactsList::findOrFail($contactsListId);
|
||||
$contactsList->contacts()->detach($request->get('contacts_ids'));
|
||||
|
||||
return redirect()->route('admin.contacts_lists.edit', $contactsList->id);
|
||||
}
|
||||
}
|
||||
101
flexiapi/app/Http/Controllers/Admin/ContactsListController.php
Normal file
101
flexiapi/app/Http/Controllers/Admin/ContactsListController.php
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
/*
|
||||
Flexisip Account Manager is a set of tools to manage SIP accounts.
|
||||
Copyright (C) 2023 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\Http\Controllers\Admin;
|
||||
|
||||
use App\ContactsList;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class ContactsListController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
return view('admin.contacts_list.index', [
|
||||
'contacts_lists' => ContactsList::orderBy('updated_at', $request->get('updated_at_order', 'desc'))
|
||||
->paginate(20)
|
||||
->appends($request->query()),
|
||||
'updated_at_order' => $request->get('updated_at_order') == 'desc' ? 'asc' : 'desc'
|
||||
]);
|
||||
}
|
||||
|
||||
public function show(int $id)
|
||||
{
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
return view('admin.contacts_list.create_edit', [
|
||||
'contacts_list' => new ContactsList,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required',
|
||||
'description' => 'required'
|
||||
]);
|
||||
|
||||
$contactsList = new ContactsList;
|
||||
$contactsList->title = $request->get('title');
|
||||
$contactsList->description = $request->get('description');
|
||||
$contactsList->save();
|
||||
|
||||
return redirect()->route('admin.contacts_lists.edit', $contactsList->id);
|
||||
}
|
||||
|
||||
public function edit(int $id)
|
||||
{
|
||||
return view('admin.contacts_list.create_edit', [
|
||||
'contacts_list' => ContactsList::findOrFail($id),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, int $id)
|
||||
{
|
||||
$request->validate([
|
||||
'title' => 'required',
|
||||
'description' => 'required'
|
||||
]);
|
||||
|
||||
$contactsList = ContactsList::findOrFail($id);
|
||||
$contactsList->title = $request->get('title');
|
||||
$contactsList->description = $request->get('description');
|
||||
$contactsList->save();
|
||||
|
||||
return redirect()->route('admin.contacts_lists.index');
|
||||
}
|
||||
|
||||
|
||||
public function delete(int $id)
|
||||
{
|
||||
return view('admin.contacts_list.delete', [
|
||||
'contacts_list' => ContactsList::findOrFail($id),
|
||||
]);
|
||||
}
|
||||
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
$contactsList = ContactsList::findOrFail($request->get('contacts_lists_id'));
|
||||
$contactsList->delete();
|
||||
|
||||
return redirect()->route('admin.contacts_lists.index');
|
||||
}
|
||||
}
|
||||
|
|
@ -20,24 +20,17 @@
|
|||
namespace App\Http\Controllers\Api\Account;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
class ContactController extends Controller
|
||||
{
|
||||
private $selected = ['id', 'username', 'domain', 'activated', 'dtmf_protocol'];
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
return $request->user()->contacts()->select($this->selected)->get();
|
||||
return resolveUserContacts($request)->get();
|
||||
}
|
||||
|
||||
public function show(Request $request, string $sip)
|
||||
{
|
||||
return $request->user()
|
||||
->contacts()
|
||||
->select($this->selected)
|
||||
->sip($sip)
|
||||
->firstOrFail();
|
||||
return resolveUserContacts($request)->sip($sip)->firstOrFail();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
flexiapi/database/factories/ContactsListFactory.php
Normal file
16
flexiapi/database/factories/ContactsListFactory.php
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
|
||||
class ContactsListFactory extends Factory
|
||||
{
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'title' => $this->faker->title,
|
||||
'description' => $this->faker->paragraph,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::dropIfExists('contacts_list_contact');
|
||||
Schema::dropIfExists('account_contacts_list');
|
||||
Schema::dropIfExists('contacts_lists');
|
||||
|
||||
Schema::create('contacts_lists', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('title');
|
||||
$table->text('description');
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('account_contacts_list', function (Blueprint $table) {
|
||||
$table->integer('account_id')->unsigned();
|
||||
$table->bigInteger('contacts_list_id')->unsigned();
|
||||
$table->foreign('account_id')->references('id')
|
||||
->on('accounts')->onDelete('cascade');
|
||||
$table->foreign('contacts_list_id')->references('id')
|
||||
->on('contacts_lists')->onDelete('cascade');
|
||||
$table->unique(['account_id', 'contacts_list_id']);
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('contacts_list_contact', function (Blueprint $table) {
|
||||
$table->integer('contact_id')->unsigned();
|
||||
$table->bigInteger('contacts_list_id')->unsigned();
|
||||
$table->foreign('contact_id')->references('id')
|
||||
->on('accounts')->onDelete('cascade');
|
||||
$table->foreign('contacts_list_id')->references('id')
|
||||
->on('contacts_lists')->onDelete('cascade');
|
||||
$table->unique(['contact_id', 'contacts_list_id']);
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::dropIfExists('account_contacts_list');
|
||||
Schema::dropIfExists('contacts_list_contact');
|
||||
Schema::dropIfExists('contacts_lists');
|
||||
}
|
||||
};
|
||||
193
flexiapi/public/css/far.css
vendored
193
flexiapi/public/css/far.css
vendored
|
|
@ -96,11 +96,26 @@ body.show_menu {
|
|||
}
|
||||
|
||||
p,
|
||||
a {
|
||||
a,
|
||||
ul li,
|
||||
pre {
|
||||
font-size: 1.5rem;
|
||||
color: var(--second-7);
|
||||
}
|
||||
|
||||
ul li {
|
||||
margin-left: 2rem;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
ul li ul li {
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
||||
ul li ul li ul li {
|
||||
list-style-type: square;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
|
@ -114,7 +129,13 @@ p i {
|
|||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
code {
|
||||
color: var(--second-6);
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
p>a:not(.btn),
|
||||
table tr td a:hover,
|
||||
label>a {
|
||||
text-decoration: underline;
|
||||
color: var(--main-5);
|
||||
|
|
@ -134,6 +155,20 @@ body.welcome content {
|
|||
max-width: 1024px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border-bottom: 1px solid var(--grey-3);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
hr.clear {
|
||||
clear: both;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
a.permalink {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
/** Tabs **/
|
||||
|
||||
ul.tabs {
|
||||
|
|
@ -148,6 +183,7 @@ ul.tabs li {
|
|||
font-weight: 800;
|
||||
color: var(--main-6);
|
||||
border-bottom: 2px solid transparent;
|
||||
list-style-type: none;
|
||||
line-height: 4rem;
|
||||
font-size: 3rem;
|
||||
margin: 0 1rem;
|
||||
|
|
@ -238,7 +274,7 @@ header nav a#logo span {
|
|||
header nav a#logo {
|
||||
position: absolute;
|
||||
left: calc(50% - 1.5rem);
|
||||
top: 0.75rem;
|
||||
top: 1.5rem;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -275,6 +311,25 @@ content section {
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
content section header {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
content section header p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
content section header form {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
content section header > *.oppose {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
content nav + section {
|
||||
min-width: calc(80% - 20rem);
|
||||
}
|
||||
|
|
@ -309,6 +364,33 @@ content > nav a {
|
|||
margin-left: 2rem;
|
||||
padding-right: 2rem;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
content > nav a.current {
|
||||
background-color: white;
|
||||
border-radius: 4rem;
|
||||
color: var(--main-5);
|
||||
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
content > nav a.current:after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background-color: white;
|
||||
border-radius: 1rem;
|
||||
position: absolute;
|
||||
left: -2rem;
|
||||
top: calc(50% - 0.5rem);
|
||||
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
content > nav a i {
|
||||
margin: 0 1rem;
|
||||
margin-left: 2rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
|
|
@ -326,37 +408,19 @@ content > nav a {
|
|||
transform: translateX(-100%);
|
||||
}
|
||||
|
||||
content > nav a {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
content > nav a.current:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
body.show_menu content > nav {
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
content > nav a.current {
|
||||
background-color: white;
|
||||
border-radius: 4rem;
|
||||
color: var(--main-5);
|
||||
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
content > nav a.current:before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
background-color: white;
|
||||
border-radius: 1rem;
|
||||
position: absolute;
|
||||
left: -2rem;
|
||||
top: 50% - 0.5rem;
|
||||
box-shadow: 0 0 1rem rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
content > nav a i {
|
||||
margin: 0 1rem;
|
||||
margin-left: 2rem;
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
/** Footer **/
|
||||
|
||||
body.welcome::after {
|
||||
|
|
@ -378,7 +442,6 @@ h1 {
|
|||
line-height: 4rem;
|
||||
font-weight: 800;
|
||||
color: var(--second-6);
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: 1rem;
|
||||
|
|
@ -401,6 +464,18 @@ h2 i {
|
|||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.75rem;
|
||||
color: var(--second-6);
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: 1.6rem;
|
||||
color: var(--second-9);
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
/** Badge **/
|
||||
|
||||
.badge {
|
||||
|
|
@ -420,13 +495,25 @@ table {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
table tr td a {
|
||||
display: block;
|
||||
}
|
||||
|
||||
table tr td,
|
||||
table tr th{
|
||||
table tr th {
|
||||
line-height: 4rem;
|
||||
padding: 0 2rem;
|
||||
padding: 0 1rem;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
table tr td.line,
|
||||
table tr th.line {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 0;
|
||||
}
|
||||
|
||||
table tr th,
|
||||
table tr th a {
|
||||
text-transform: uppercase;
|
||||
|
|
@ -449,6 +536,29 @@ table tr:nth-child(2n) {
|
|||
background-color: var(--grey-1);
|
||||
}
|
||||
|
||||
table tr.empty {
|
||||
background-color: var(--grey-2);
|
||||
text-align: center;
|
||||
color: var(--second-4);
|
||||
}
|
||||
|
||||
table tr.empty td {
|
||||
font-size: 2rem;
|
||||
padding-bottom: 9rem;
|
||||
}
|
||||
|
||||
table tr.empty td:before {
|
||||
content: '\e5c9';
|
||||
font-family: 'Material Icons';
|
||||
font-size: 8rem;
|
||||
color: var(--second-4);
|
||||
display: block;
|
||||
text-align: center;
|
||||
margin: 12rem;
|
||||
margin-bottom: 1rem;
|
||||
line-height: 8rem;
|
||||
}
|
||||
|
||||
/* Display/hide */
|
||||
|
||||
.on_mobile {
|
||||
|
|
@ -476,6 +586,21 @@ table tr:nth-child(2n) {
|
|||
}
|
||||
}
|
||||
|
||||
/** Chips **/
|
||||
|
||||
.chip {
|
||||
display: inline-block;
|
||||
background-color: var(--grey-1);
|
||||
border: 1px solid var(--grey-2);
|
||||
border-radius: 3rem;
|
||||
line-height: 2.5rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.chip i {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/** Pagination **/
|
||||
|
||||
ul.pagination {
|
||||
|
|
@ -517,3 +642,9 @@ ul.pagination li:not(.disabled):not(.active) .page-link:hover {
|
|||
ul.pagination li:not(.disabled) .page-link:hover {
|
||||
border-color: var(--main-5);
|
||||
}
|
||||
|
||||
/** List Toggle */
|
||||
|
||||
select.list_toggle {
|
||||
display: none;
|
||||
}
|
||||
55
flexiapi/public/css/form.css
vendored
55
flexiapi/public/css/form.css
vendored
|
|
@ -10,7 +10,7 @@
|
|||
line-height: 2rem;
|
||||
padding: 1rem 2rem;
|
||||
color: white;
|
||||
margin: 0 1rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn i {
|
||||
|
|
@ -58,10 +58,24 @@
|
|||
color: white;
|
||||
}
|
||||
|
||||
.btn.btn-tertiary {
|
||||
background-color: var(--main-1);
|
||||
border-color: transparent;
|
||||
color: var(--main-5);
|
||||
}
|
||||
|
||||
.btn.btn-tertiary:hover {
|
||||
background-color: var(--main-2);
|
||||
}
|
||||
|
||||
.btn.btn-tertiary:active {
|
||||
background-color: var(--main-3);
|
||||
}
|
||||
|
||||
form {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1.5rem 0.5rem;
|
||||
gap: 1.5rem 2.5rem;
|
||||
}
|
||||
|
||||
form.inline {
|
||||
|
|
@ -85,6 +99,12 @@ form h2 {
|
|||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
form .disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
filter: blur(0.25rem);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1024px) {
|
||||
form div {
|
||||
grid-column: 1/-1;
|
||||
|
|
@ -110,6 +130,7 @@ form input[required]+label:after {
|
|||
}
|
||||
|
||||
form input:not([type=checkbox]) ~ label,
|
||||
form textarea ~ label,
|
||||
form select ~ label {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
|
@ -127,12 +148,14 @@ form div .btn.oppose {
|
|||
}
|
||||
|
||||
form div input,
|
||||
form div textarea,
|
||||
form div select {
|
||||
padding: 1rem 2rem;
|
||||
background-color: var(--grey-1);
|
||||
border-radius: 3rem;
|
||||
border: 1px solid var(--grey-2);
|
||||
font-size: 1.5rem;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
form div select {
|
||||
|
|
@ -158,19 +181,24 @@ form div.select:after {
|
|||
line-height: 4rem;
|
||||
}
|
||||
|
||||
form div input[disabled] {
|
||||
form div input[disabled],
|
||||
form div textarea[disabled] {
|
||||
border-color: var(--grey-4);
|
||||
color: var(--grey-4);
|
||||
background-color: var(--grey-2);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
form div input[type=checkbox] {
|
||||
margin-right: 1rem;
|
||||
input[type=checkbox] {
|
||||
accent-color: var(--main-5);
|
||||
}
|
||||
|
||||
form div input[type=checkbox] {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
form div input:not([type=checkbox]):not([type=radio]):not(.btn),
|
||||
form div textarea,
|
||||
form div select {
|
||||
margin-top: 2.5rem;
|
||||
box-sizing: border-box;
|
||||
|
|
@ -193,24 +221,33 @@ form div input:autofill {
|
|||
}
|
||||
|
||||
form div input:hover,
|
||||
form div textarea:hover,
|
||||
form div select:hover {
|
||||
border-color: var(--second-4);
|
||||
}
|
||||
|
||||
form div input:focus-visible, form div input:active {
|
||||
form div input:focus-visible,
|
||||
form div input:active,
|
||||
form div textarea:focus-visible,
|
||||
form div textarea:active {
|
||||
color: var(--main-5);
|
||||
border-color: var(--main-5);
|
||||
}
|
||||
|
||||
form div input:focus-visible+label, form div input:active+label {
|
||||
form div input:focus-visible+label,
|
||||
form div input:active+label,
|
||||
form div textarea:focus-visible+label,
|
||||
form div textarea:active+label {
|
||||
color: var(--main-5);
|
||||
}
|
||||
|
||||
form div input:invalid {
|
||||
form div input:invalid,
|
||||
form div textarea:invalid {
|
||||
border-color: var(--danger-6);
|
||||
color: var(--danger-5);
|
||||
}
|
||||
|
||||
form div input:invalid+label {
|
||||
form div input:invalid+label,
|
||||
form div textarea:invalid+label {
|
||||
color: var(--danger-5);
|
||||
}
|
||||
|
|
@ -1,94 +1,93 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
<header>
|
||||
<h1><i class="material-icons">dashboard</i> Dashboard</h1>
|
||||
</header>
|
||||
|
||||
<h1><i class="material-icons">dashboard</i> Dashboard</h1>
|
||||
<p>
|
||||
<i class="material-icons">email</i>
|
||||
@if (!empty($account->email))
|
||||
{{ $account->email }}
|
||||
@else
|
||||
No email yet
|
||||
<p>
|
||||
<i class="material-icons">email</i>
|
||||
@if (!empty($account->email))
|
||||
{{ $account->email }}
|
||||
@else
|
||||
No email yet
|
||||
@endif
|
||||
<a href="{{ route('account.email.change') }}">Change my current account email</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<i class="material-icons">call</i>
|
||||
@if (!empty($account->phone))
|
||||
{{ $account->phone }}
|
||||
@else
|
||||
No phone yet
|
||||
@endif
|
||||
<a href="{{ route('account.phone.change') }}">Change my current account phone</a>
|
||||
</p>
|
||||
@if (config('app.devices_management') == true)
|
||||
<p>
|
||||
<i class="material-icons">laptop</i>
|
||||
<a href="{{ route('account.device.index') }}">Manage my devices</a>
|
||||
</p>
|
||||
@endif
|
||||
<a href="{{ route('account.email.change') }}">Change my current account email</a>
|
||||
</p>
|
||||
<p>
|
||||
<i class="material-icons">lock</i>
|
||||
<a href="{{ route('account.password') }}">
|
||||
@if ($account->passwords()->count() > 0)
|
||||
Change my password
|
||||
@else
|
||||
Set my password
|
||||
@endif
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
|
||||
<p>
|
||||
<i class="material-icons">call</i>
|
||||
@if (!empty($account->phone))
|
||||
{{ $account->phone }}
|
||||
@else
|
||||
No phone yet
|
||||
<p>
|
||||
<i class="material-icons">delete</i>
|
||||
<a href="{{ route('account.delete') }}">Delete my account</a>
|
||||
</p>
|
||||
|
||||
<h2><i class="material-icons">person</i> Account information</h2>
|
||||
|
||||
<p><i class="material-icons">alternate_email</i> SIP address: sip:{{ $account->identifier }}</p>
|
||||
<p><i class="material-icons">person</i> Username: {{ $account->username }}</p>
|
||||
<p><i class="material-icons">dns</i> Domain: {{ $account->domain }}</p>
|
||||
|
||||
@if (!empty(config('app.proxy_registrar_address')))
|
||||
<p><i class="material-icons">lan</i> Proxy/registrar address: sip:{{ config('app.proxy_registrar_address') }}</p>
|
||||
@endif
|
||||
<a href="{{ route('account.phone.change') }}">Change my current account phone</a>
|
||||
</p>
|
||||
@if (config('app.devices_management') == true)
|
||||
<p>
|
||||
<i class="material-icons">laptop</i>
|
||||
<a href="{{ route('account.device.index') }}">Manage my devices</a>
|
||||
</p>
|
||||
@endif
|
||||
<p>
|
||||
<i class="material-icons">lock</i>
|
||||
<a href="{{ route('account.password') }}" >
|
||||
@if ($account->passwords()->count() > 0)
|
||||
Change my password
|
||||
@else
|
||||
Set my password
|
||||
@if (!empty(config('app.transport_protocol_text')))
|
||||
<p><i class="material-icons">settings_ethernet</i> Transport: {{ config('app.transport_protocol_text') }} </p>
|
||||
@endif
|
||||
</a>
|
||||
</p>
|
||||
<p>
|
||||
|
||||
<p>
|
||||
<i class="material-icons">delete</i>
|
||||
<a href="{{ route('account.delete') }}">Delete my account</a>
|
||||
</p>
|
||||
<!--<h3 class="mt-3">Automatic authentication</h3>
|
||||
|
||||
<h2><i class="material-icons">person</i> Account information</h2>
|
||||
<p>You can automatically authenticate another device on this panel by flashing the following QR Code.
|
||||
Once generated the QR Code stays valid for a few minutes.</p>
|
||||
|
||||
<p><i class="material-icons">alternate_email</i> SIP address: sip:{{ $account->identifier }}</p>
|
||||
<p><i class="material-icons">person</i> Username: {{ $account->username }}</p>
|
||||
<p><i class="material-icons">dns</i> Domain: {{ $account->domain }}</p>
|
||||
|
||||
@if (!empty(config('app.proxy_registrar_address')))
|
||||
<p><i class="material-icons">lan</i> Proxy/registrar address: sip:{{ config('app.proxy_registrar_address') }}</p>
|
||||
@endif
|
||||
@if (!empty(config('app.transport_protocol_text')))
|
||||
<p><i class="material-icons">settings_ethernet</i> Transport: {{ config('app.transport_protocol_text') }} </p>
|
||||
@endif
|
||||
|
||||
<!--<h3 class="mt-3">Automatic authentication</h3>
|
||||
|
||||
<p>You can automatically authenticate another device on this panel by flashing the following QR Code.
|
||||
Once generated the QR Code stays valid for a few minutes.</p>
|
||||
|
||||
@foreach ($account->authTokens()->valid()->get() as $authToken)
|
||||
@foreach ($account->authTokens()->valid()->get() as $authToken)
|
||||
<img src="{{ route('auth_tokens.qrcode', ['token' => $authToken->token]) }}">
|
||||
@endforeach
|
||||
@endforeach
|
||||
|
||||
{!! Form::open(['route' => 'account.auth_tokens.create']) !!}
|
||||
<button type="submit" class="btn btn-primary">Generate</button>
|
||||
{!! Form::close() !!}-->
|
||||
{!! Form::open(['route' => 'account.auth_tokens.create']) !!}
|
||||
<button type="submit" class="btn btn-primary">Generate</button>
|
||||
{!! Form::close() !!}-->
|
||||
|
||||
<h2><i class="material-icons">key</i>API Key</h2>
|
||||
<h2><i class="material-icons">key</i>API Key</h2>
|
||||
|
||||
<p>You can generate an API key and use it to request the different API endpoints, <a href="{{ route('api') }}">check the related API documentation</a> to know how to use that key.</p>
|
||||
<p>You can generate an API key and use it to request the different API endpoints, <a href="{{ route('api') }}">check
|
||||
the related API documentation</a> to know how to use that key.</p>
|
||||
|
||||
{!! Form::open(['route' => 'account.api_key.generate']) !!}
|
||||
{!! Form::open(['route' => 'account.api_key.generate']) !!}
|
||||
<div>
|
||||
<input readonly class="form-control" placeholder="No key yet, press Generate"
|
||||
@if ($account->apiKey)
|
||||
value="{{ $account->apiKey->key }}"
|
||||
@endif
|
||||
>
|
||||
@if ($account->apiKey) value="{{ $account->apiKey->key }}" @endif>
|
||||
<label>Key</label>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-primary">Generate</button>
|
||||
</div>
|
||||
{!! Form::close() !!}
|
||||
{!! Form::close() !!}
|
||||
|
||||
@include('parts.account_variables', ['account' => $account])
|
||||
|
||||
@endsection
|
||||
@include('parts.account_variables', ['account' => $account])
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -6,11 +6,11 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<table class="table table-responsive-md">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">User Agent</th>
|
||||
<th scope="col"></th>
|
||||
<th>User Agent</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<a href="{{ route('admin.account.index') }}">Accounts</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item" aria-current="page">
|
||||
<a href="{{ route('admin.account.show', $account->id) }}">{{ $account->identifier }}</a>
|
||||
<a href="{{ route('admin.account.edit', $account->id) }}">{{ $account->identifier }}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
Types
|
||||
|
|
@ -17,9 +17,9 @@
|
|||
<h2>Add a Type to the Account</h2>
|
||||
|
||||
@if ($account_types->count() == 0)
|
||||
<div class="alert alert-secondary" role="alert">
|
||||
<p>
|
||||
No Account Type to add
|
||||
</div>
|
||||
</p>
|
||||
@else
|
||||
|
||||
{!! Form::model($account, [
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<a href="{{ route('admin.account.index') }}">Accounts</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item" aria-current="page">
|
||||
<a href="{{ route('admin.account.show', $account->id) }}">{{ $account->identifier }}</a>
|
||||
<a href="{{ route('admin.account.edit', $account->id) }}">{{ $account->identifier }}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
Actions
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<a href="{{ route('admin.account.index') }}">Accounts</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item" aria-current="page">
|
||||
<a href="{{ route('admin.account.show', $action->account) }}">{{ $action->account->identifier }}</a>
|
||||
<a href="{{ route('admin.account.edit', $action->account) }}">{{ $action->account->identifier }}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
Actions
|
||||
|
|
|
|||
|
|
@ -1,33 +1,17 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item" aria-current="page">
|
||||
<a href="{{ route('admin.account.index') }}">Accounts</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item" aria-current="page">
|
||||
<a href="{{ route('admin.account.show', $account->id) }}">{{ $account->identifier }}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
Contacts
|
||||
</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<h2>Add a Contact to the Account</h2>
|
||||
|
||||
<h2>Add a Contact to the Account</h2>
|
||||
|
||||
{!! Form::model($account, [
|
||||
'route' => ['admin.account.contact.store', $account->id],
|
||||
'method' => 'post'
|
||||
]) !!}
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-12">
|
||||
{!! Form::label('sip', 'Adresse SIP') !!}
|
||||
{!! Form::text('sip', null, ['class' => 'form-control', 'placeholder' => 'username@server.com']); !!}
|
||||
<form method="POST" action="{{ route('admin.account.contact.store', $account->id) }}" accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('post')
|
||||
<div>
|
||||
<input placeholder="username@server.com" name="sip" type="text" id="sip">
|
||||
<label for="sip">SIP Address</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!! Form::submit('Add', ['class' => 'btn btn-success btn-centered']) !!}
|
||||
{!! Form::close() !!}
|
||||
|
||||
@endsection
|
||||
<div>
|
||||
<input class="btn" type="submit" value="Add">
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<a href="{{ route('admin.account.index') }}">Accounts</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item" aria-current="page">
|
||||
<a href="{{ route('admin.account.show', $account) }}">{{ $account->identifier }}</a>
|
||||
<a href="{{ route('admin.account.edit', $account) }}">{{ $account->identifier }}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">
|
||||
Contacts
|
||||
|
|
|
|||
|
|
@ -2,31 +2,33 @@
|
|||
|
||||
@section('content')
|
||||
<div>
|
||||
<a class="btn oppose btn-secondary" href="{{ route('admin.account.delete', $account->id) }}">
|
||||
<i class="material-icons">delete</i>
|
||||
Delete
|
||||
</a>
|
||||
@if ($account->id)
|
||||
<a class="btn oppose 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
|
||||
<p title="{{ $account->updated_at }}">Updated on {{ $account->updated_at->format('d/m/Y') }}
|
||||
@else
|
||||
<h1><i class="material-icons">people</i> Create an account</h1>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<form method="POST"
|
||||
action="{{ $account->id ? route('admin.account.update', $account->id) : route('admin.account.store') }}"
|
||||
accept-charset="UTF-8">
|
||||
id="create_edit" accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method($account->id ? 'put' : 'post')
|
||||
<h2>Connexion</h2>
|
||||
<div>
|
||||
<input placeholder="Username" required="required" name="username" type="text" value="{{ $account->username }}">
|
||||
<input placeholder="Username" required="required" name="username" type="text"
|
||||
value="{{ $account->username }}">
|
||||
<label for="username">Username</label>
|
||||
@include('parts.errors', ['name' => 'username'])
|
||||
</div>
|
||||
<div>
|
||||
<input placeholder="domain.com" @if (config('app.admins_manage_multi_domains')) required @else disabled @endif name="domain" type="text" value="{{ $account->domain ?? config('app.sip_domain') }}">
|
||||
<input placeholder="domain.com" @if (config('app.admins_manage_multi_domains')) required @else disabled @endif name="domain"
|
||||
type="text" value="{{ $account->domain ?? config('app.sip_domain') }}">
|
||||
<label for="domain">Domain</label>
|
||||
</div>
|
||||
|
||||
|
|
@ -65,14 +67,14 @@
|
|||
<h2>Other information</h2>
|
||||
|
||||
<div>
|
||||
<input name="activated" type="checkbox" @if ($account->activated)checked @endif>
|
||||
<input name="activated" type="checkbox" @if ($account->activated) checked @endif>
|
||||
<label>Activated</label>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input name="role" value="admin" type="radio" @if ($account->admin)checked @endif>
|
||||
<input name="role" value="admin" type="radio" @if ($account->admin) checked @endif>
|
||||
<p>Admin</p>
|
||||
<input name="role" value="end_user" type="radio" @if (!$account->admin)checked @endif>
|
||||
<input name="role" value="end_user" type="radio" @if (!$account->admin) checked @endif>
|
||||
<p>End user</p>
|
||||
<label>Role</label>
|
||||
</div>
|
||||
|
|
@ -80,16 +82,161 @@
|
|||
<div class="select">
|
||||
<select name="dtmf_protocol">
|
||||
@foreach ($protocols as $value => $name)
|
||||
<option value="{{ $value }}" @if( $account->dtmf_protocol == $value )selected="selected"@endif>{{ $name }}</option>
|
||||
<option value="{{ $value }}" @if ($account->dtmf_protocol == $value) selected="selected" @endif>
|
||||
{{ $name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<label for="dtmf_protocol">DTMF Protocol</label>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="large">
|
||||
<input class="btn oppose" type="submit" value="{{ $account->id ? 'Update' : 'Create' }}">
|
||||
</div>
|
||||
|
||||
</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)
|
||||
<h2>Contacts Lists</h2>
|
||||
|
||||
@foreach ($account->contactsLists as $contactsList)
|
||||
<p class="chip">
|
||||
<a
|
||||
href="{{ route('admin.contacts_lists.edit', ['contacts_list_id' => $contactsList->id]) }}">{{ $contactsList->title }}</a>
|
||||
<a
|
||||
href="{{ route('admin.account.contacts_lists.detach', ['account_id' => $account->id, 'contacts_list_id' => $contactsList->id]) }}">
|
||||
<i class="material-icons">close</i>
|
||||
</a>
|
||||
</p>
|
||||
@endforeach
|
||||
|
||||
<br />
|
||||
|
||||
@if ($contacts_lists->isNotEmpty())
|
||||
<form method="POST" action="{{ route('admin.account.contacts_lists.attach', $account->id) }}"
|
||||
accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('post')
|
||||
|
||||
<div class="select">
|
||||
<select name="contacts_list_id">
|
||||
@foreach ($contacts_lists as $contacts_list)
|
||||
<option value="{{ $contacts_list->id }}">
|
||||
{{ $contacts_list->title }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<label for="contacts_list_id">Add a Contacts lists</label>
|
||||
</div>
|
||||
<div>
|
||||
<input class="btn btn-tertiary" type="submit" value="Add">
|
||||
</div>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
<h2>Individual contacts</h2>
|
||||
|
||||
@foreach ($account->contacts as $contact)
|
||||
<p class="chip">
|
||||
<a href="{{ route('admin.account.edit', $account) }}">{{ $contact->identifier }}</a>
|
||||
<a href="{{ route('admin.account.contact.delete', [$account, $contact->id]) }}">
|
||||
<i class="material-icons">close</i>
|
||||
</a>
|
||||
</p>
|
||||
@endforeach
|
||||
|
||||
<br />
|
||||
<a class="btn btn-tertiary" href="{{ route('admin.account.contact.create', $account) }}">Add</a>
|
||||
|
||||
<hr class="large">
|
||||
|
||||
<h2 id="provisioning">Provisioning</h2>
|
||||
|
||||
@if ($account->provisioning_token)
|
||||
<p>Share the following picture with the user or the one-time-use link bellow.</p>
|
||||
|
||||
<img style="max-width: 15rem;" src="{{ route('provisioning.qrcode', $account->provisioning_token) }}">
|
||||
|
||||
<form class="inline">
|
||||
<div>
|
||||
<input type="text" style="min-width: 40rem;" readonly
|
||||
value="{{ route('provisioning.show', $account->provisioning_token) }}">
|
||||
<small>The following link can only be visited once</small>
|
||||
</div>
|
||||
<div>
|
||||
<a class="btn" href="{{ route('admin.account.provision', $account->id) }}">Renew the provision
|
||||
link</a>
|
||||
<small>The current one will be unavailable</small>
|
||||
</div>
|
||||
</form>
|
||||
<p class="mt-3">
|
||||
</p>
|
||||
@else
|
||||
<p class="mt-3">
|
||||
<a class="btn btn-light" href="{{ route('admin.account.provision', $account->id) }}">Generate a provision
|
||||
link</a>
|
||||
</p>
|
||||
@endif
|
||||
|
||||
<h2>External Account</h2>
|
||||
|
||||
@if ($account->externalAccount)
|
||||
<p>
|
||||
<b>Identifier:</b> {{ $account->externalAccount->identifier }}<br />
|
||||
</p>
|
||||
@else
|
||||
<a class="btn btn-sm @if ($external_accounts_count == 0)disabled @endif" href="{{ route('admin.account.external_account.attach', $account->id) }}">Attach an External Account ({{ $external_accounts_count}} left)</a>
|
||||
@endif
|
||||
|
||||
<h2>Actions</h2>
|
||||
|
||||
@if ($account->dtmf_protocol)
|
||||
|
||||
<table class="table">
|
||||
<tbody>
|
||||
@foreach ($account->actions as $action)
|
||||
<tr>
|
||||
<th scope="row">{{ $action->key }}</th>
|
||||
<td>{{ $action->code }}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm mr-2" href="{{ route('admin.account.action.edit', [$account, $action->id]) }}">Edit</a>
|
||||
<a class="btn btn-sm mr-2" href="{{ route('admin.account.action.delete', [$account, $action->id]) }}">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a class="btn btn-sm" href="{{ route('admin.account.action.create', $account) }}">Add</a>
|
||||
|
||||
@else
|
||||
<p>To manage actions, you must configure the DTMF protocol in the account settings.</p>
|
||||
@endif
|
||||
|
||||
<h2>Types</h2>
|
||||
|
||||
<table class="table">
|
||||
<tbody>
|
||||
@foreach ($account->types as $type)
|
||||
<tr>
|
||||
<th scope="row">{{ $type->key }}</th>
|
||||
<td>
|
||||
{!! Form::open(['route' => ['admin.account.account_type.destroy', $account, $type->id], 'method' => 'delete']) !!}
|
||||
{!! Form::submit('Delete', ['class' => 'btn btn-sm mr-2']) !!}
|
||||
{!! Form::close() !!}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a class="btn btn-sm" href="{{ route('admin.account.account_type.create', $account) }}">Add</a>
|
||||
|
||||
|
||||
|
||||
@endif
|
||||
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -1,15 +1,21 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
<h2>Delete an account</h2>
|
||||
@section('content')
|
||||
<h2>Delete an account</h2>
|
||||
|
||||
{!! Form::open(['route' => 'admin.account.destroy', 'method' => 'delete']) !!}
|
||||
{!! Form::open(['route' => 'admin.account.destroy', 'method' => 'delete']) !!}
|
||||
|
||||
<p>You are going to permanently delete the following account. Please confirm your action.</p>
|
||||
<p><b>{{ $account->identifier }}</b></p>
|
||||
<div class="large">
|
||||
<p>You are going to permanently delete the following account. Please confirm your action.<br />
|
||||
<b>{{ $account->identifier }}</b>
|
||||
</p>
|
||||
|
||||
{!! Form::hidden('account_id', $account->id) !!}
|
||||
{!! Form::hidden('account_id', $account->id) !!}
|
||||
</div>
|
||||
<div>
|
||||
{!! Form::submit('Delete', ['class' => 'btn']) !!}
|
||||
|
||||
{!! Form::submit('Delete', ['class' => 'btn btn-danger btn-centered']) !!}
|
||||
{!! Form::close() !!}
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
{!! Form::close() !!}
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
@section('content')
|
||||
|
||||
<div>
|
||||
<header>
|
||||
<h1><i class="material-icons">people</i> Account</h1>
|
||||
<a class="btn oppose" href="{{ route('admin.account.create') }}">
|
||||
<i class="material-icons">add_circle</i>
|
||||
Create
|
||||
</a>
|
||||
<h1><i class="material-icons">people</i> Account</h1>
|
||||
</div>
|
||||
</header>
|
||||
<div>
|
||||
<form class="inline" method="POST" action="{{ route('admin.account.search')}}" accept-charset="UTF-8">
|
||||
@csrf
|
||||
|
|
@ -27,14 +27,12 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
<br />
|
||||
|
||||
<table class="table table-responsive-md">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Identifier (email)</th>
|
||||
<th scope="col"></th>
|
||||
<th scope="col">
|
||||
<th>Identifier (email)</th>
|
||||
<th></th>
|
||||
<th>
|
||||
<a href="{{ route('admin.account.index', ['updated_at_order' => $updated_at_order]) }}">
|
||||
Updated
|
||||
@if ($updated_at_order == 'desc')
|
||||
|
|
@ -71,7 +69,7 @@
|
|||
<span class="badge badge-info">SHA256</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $account->created_at}}</td>
|
||||
<td>{{ $account->updated_at}}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -1,126 +0,0 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item" aria-current="page">
|
||||
<a href="{{ route('admin.account.index') }}">Accounts</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item" aria-current="page">
|
||||
<a href="{{ route('admin.account.show', $account->id) }}">{{ $account->identifier }}</a>
|
||||
</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
|
||||
<a class="btn oppose" href="{{ route('admin.account.delete', $account->id) }}">Delete</a>
|
||||
<a class="btn oppose" href="{{ route('admin.account.edit', $account->id) }}">Edit</a>
|
||||
|
||||
<h1>Account</h1>
|
||||
|
||||
<p>
|
||||
<b>Id:</b> {{ $account->id }}<br />
|
||||
<b>Identifier:</b> {{ $account->identifier }}<br />
|
||||
<b>Email:</b> <a href="mailto:{{ $account->email }}">{{ $account->email }}</a><br />
|
||||
<b>DTMF Protocol:</b> @if ($account->dtmf_protocol) {{ $account->resolvedDtmfProtocol }}@endif<br />
|
||||
@if ($account->alias)<b>Phone number:</b> {{ $account->phone }}<br />@endif
|
||||
@if ($account->group)<b>Group:</b> {{ $account->group }}<br />@endif
|
||||
@if ($account->display_name)<b>Display name:</b> {{ $account->display_name }}<br />@endif
|
||||
</p>
|
||||
|
||||
@if ($account->sha256Password)
|
||||
<span class="badge badge-info">SHA256</span>
|
||||
@endif
|
||||
|
||||
<br />
|
||||
|
||||
<h3 class="mt-3">External Account</h3>
|
||||
|
||||
@if ($account->externalAccount)
|
||||
<p>
|
||||
<b>Identifier:</b> {{ $account->externalAccount->identifier }}<br />
|
||||
</p>
|
||||
@else
|
||||
<a class="btn btn-sm @if ($external_accounts_count == 0)disabled @endif" href="{{ route('admin.account.external_account.attach', $account->id) }}">Attach an External Account ({{ $external_accounts_count}} left)</a>
|
||||
@endif
|
||||
|
||||
<h3 class="mt-3">Contacts</h3>
|
||||
|
||||
<table class="table">
|
||||
<tbody>
|
||||
@foreach ($account->contacts as $contact)
|
||||
<tr>
|
||||
<th scope="row">{{ $contact->identifier }}</th>
|
||||
<td>
|
||||
<a class="btn btn-sm mr-2" href="{{ route('admin.account.contact.delete', [$account, $contact->id]) }}">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a class="btn btn-sm" href="{{ route('admin.account.contact.create', $account) }}">Add</a>
|
||||
|
||||
<h3 class="mt-3">Actions</h3>
|
||||
|
||||
@if ($account->dtmf_protocol)
|
||||
|
||||
<table class="table">
|
||||
<tbody>
|
||||
@foreach ($account->actions as $action)
|
||||
<tr>
|
||||
<th scope="row">{{ $action->key }}</th>
|
||||
<td>{{ $action->code }}</td>
|
||||
<td>
|
||||
<a class="btn btn-sm mr-2" href="{{ route('admin.account.action.edit', [$account, $action->id]) }}">Edit</a>
|
||||
<a class="btn btn-sm mr-2" href="{{ route('admin.account.action.delete', [$account, $action->id]) }}">Delete</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a class="btn btn-sm" href="{{ route('admin.account.action.create', $account) }}">Add</a>
|
||||
|
||||
@else
|
||||
<p>To manage actions, you must configure the DTMF protocol in the account settings.</p>
|
||||
@endif
|
||||
|
||||
<h3 class="mt-3">Types</h3>
|
||||
|
||||
<table class="table">
|
||||
<tbody>
|
||||
@foreach ($account->types as $type)
|
||||
<tr>
|
||||
<th scope="row">{{ $type->key }}</th>
|
||||
<td>
|
||||
{!! Form::open(['route' => ['admin.account.account_type.destroy', $account, $type->id], 'method' => 'delete']) !!}
|
||||
{!! Form::submit('Delete', ['class' => 'btn btn-sm mr-2']) !!}
|
||||
{!! Form::close() !!}
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<a class="btn btn-sm" href="{{ route('admin.account.account_type.create', $account) }}">Add</a>
|
||||
|
||||
<h3 class="mt-3">Provisioning</h3>
|
||||
|
||||
@if ($account->provisioning_token)
|
||||
<p>Share the following picture with the user or the one-time-use link bellow.</p>
|
||||
|
||||
<img src="{{ route('provisioning.qrcode', $account->provisioning_token) }}"><br />
|
||||
|
||||
<br />
|
||||
<p>The following link can only be visited once</p>
|
||||
<input class="form-control" type="text" readonly value="{{ route('provisioning.show', $account->provisioning_token) }}">
|
||||
<p class="mt-3">
|
||||
<a class="btn btn-light mr-2" href="{{ route('admin.account.provision', $account->id) }}">Renew the provision link</a>
|
||||
The current one will be unavailable
|
||||
</p>
|
||||
@else
|
||||
<p class="mt-3">
|
||||
<a class="btn btn-light" href="{{ route('admin.account.provision', $account->id) }}">Generate a provision link</a>
|
||||
</p>
|
||||
@endif
|
||||
|
||||
@endsection
|
||||
|
|
@ -18,11 +18,11 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<table class="table table-responsive-md">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Key</th>
|
||||
<th scope="col"></th>
|
||||
<th>Key</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
<header>
|
||||
<h1><i class="material-icons">account_box</i> Contacts List | Add contacts</h1>
|
||||
<p class="oppose">
|
||||
<span class="list_toggle" data-list-id="a{{ $contacts_list->id }}"></span> selected
|
||||
</p>
|
||||
|
||||
<form method="POST"
|
||||
action="{{ route('admin.contacts_lists.contacts.store', $contacts_list->id) }}"
|
||||
accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('post')
|
||||
|
||||
<select name="contacts_ids[]" class="list_toggle" data-list-id="a{{ $contacts_list->id }}"></select>
|
||||
<input type="hidden" name="contacts_list_id" value="{{ $contacts_list->id }}">
|
||||
<input class="btn" type="submit" value="Add" onclick="Utils.clearStorageList('a{{ $contacts_list->id }}')">
|
||||
</form>
|
||||
</header>
|
||||
|
||||
<div>
|
||||
<form class="inline" method="POST" action="{{ route('admin.contacts_lists.contacts.search', $params) }}" accept-charset="UTF-8">
|
||||
@csrf
|
||||
<div>
|
||||
<input placeholder="Search by username: +1234, foo_bar…" name="search" type="text" value="{{ $params['search'] }}">
|
||||
<label for="search">Search</label>
|
||||
</div>
|
||||
<div>
|
||||
<a href="{{ route('admin.contacts_lists.contacts.add', $contacts_list->id) }}" type="reset" class="btn btn-secondary">Reset</a>
|
||||
<button type="submit" class="btn">Search</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<input type="checkbox" onchange="Utils.toggleAll(this)">
|
||||
</th>
|
||||
<th>Username</th>
|
||||
<th>
|
||||
<a href="{{ route('admin.contacts_lists.contacts.add', $params) }}">
|
||||
Updated
|
||||
@if ($params['updated_at_order'] == 'desc')
|
||||
<i class="material-icons">expand_more</i>
|
||||
@else
|
||||
<i class="material-icons">expand_less</i>
|
||||
@endif
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if ($accounts->isEmpty())
|
||||
<tr class="empty">
|
||||
<td colspan="3">No Contact</td>
|
||||
</tr>
|
||||
@endif
|
||||
@foreach ($accounts as $account)
|
||||
<tr>
|
||||
<td>
|
||||
<input class="list_toggle" type="checkbox" data-list-id="a{{ $contacts_list->id }}" data-id="{{ $account->id }}">
|
||||
</td>
|
||||
<td>
|
||||
{{ $account->identifier }}
|
||||
</td>
|
||||
<td>{{ $account->updated_at}}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ $accounts->links('pagination::bootstrap-4') }}
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
<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) }}">
|
||||
<i class="material-icons">delete</i>
|
||||
Delete
|
||||
</a>
|
||||
@else
|
||||
<h1><i class="material-icons">account_box</i> Create a Contacts List</h1>
|
||||
@endif
|
||||
</header>
|
||||
|
||||
@if ($contacts_list->id)
|
||||
<p title="{{ $contacts_list->updated_at }}">Updated on {{ $contacts_list->updated_at->format('d/m/Y') }}
|
||||
@endif
|
||||
|
||||
<form method="POST"
|
||||
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 }}">
|
||||
<label for="username">Name</label>
|
||||
@include('parts.errors', ['name' => 'title'])
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<textarea placeholder="Description" required="required" name="description">{{ $contacts_list->description }}</textarea>
|
||||
<label for="description">Description</label>
|
||||
@include('parts.errors', ['name' => 'description'])
|
||||
</div>
|
||||
|
||||
<div class="large">
|
||||
<input class="btn oppose" type="submit" value="{{ $contacts_list->id ? 'Update' : 'Create' }}">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@if ($contacts_list->id)
|
||||
<hr class="clear">
|
||||
|
||||
<header>
|
||||
<p class="oppose">
|
||||
<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">
|
||||
@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" onclick="Utils.clearStorageList('d{{ $contacts_list->id }}')">
|
||||
</form>
|
||||
|
||||
<a class="btn btn-secondary" href="{{ route('admin.contacts_lists.contacts.add', $contacts_list->id) }}">
|
||||
<i class="material-icons">add</i> Add contacts
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<table class="large">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<input type="checkbox" onchange="Utils.toggleAll(this)">
|
||||
</th>
|
||||
<th>Username</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@if ($contacts_list->contacts->isEmpty())
|
||||
<tr class="empty">
|
||||
<td colspan="2">No Contact</td>
|
||||
</tr>
|
||||
@endif
|
||||
@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 }}">
|
||||
</td>
|
||||
<td>{{ $contact->identifier }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@endif
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
<h2>Delete a Contact List</h2>
|
||||
|
||||
<form method="POST" action="{{ route('admin.contacts_lists.destroy', $contacts_list->id) }}" accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('delete')
|
||||
|
||||
<div class="large">
|
||||
<p>You are going to permanently delete the following contacts list. Please confirm your action.<br />
|
||||
<b>{{ $contacts_list->title }}</b>
|
||||
</p>
|
||||
|
||||
<input name="contacts_lists_id" type="hidden" value="{{ $contacts_list->id }}">
|
||||
</div>
|
||||
<div>
|
||||
<input class="btn" type="submit" value="Delete">
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@endsection
|
||||
49
flexiapi/resources/views/admin/contacts_list/index.blade.php
Normal file
49
flexiapi/resources/views/admin/contacts_list/index.blade.php
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('content')
|
||||
|
||||
<header>
|
||||
<h1><i class="material-icons">account_box</i> Contacts Lists</h1>
|
||||
<a class="btn oppose" href="{{ route('admin.contacts_lists.create') }}">
|
||||
<i class="material-icons">add_circle</i>
|
||||
Create
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Description</th>
|
||||
<th>Number of Contacts</th>
|
||||
<th>
|
||||
<a href="{{ route('admin.contacts_lists.index', ['updated_at_order' => $updated_at_order]) }}">
|
||||
Updated
|
||||
@if ($updated_at_order == 'desc')
|
||||
<i class="material-icons">expand_more</i>
|
||||
@else
|
||||
<i class="material-icons">expand_less</i>
|
||||
@endif
|
||||
</a>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($contacts_lists as $contacts_list)
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{{ route('admin.contacts_lists.edit', $contacts_list->id) }}">
|
||||
{{ $contacts_list->title }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="line">{{ $contacts_list->description }}</td>
|
||||
<td>{{ $contacts_list->contacts_count }}</td>
|
||||
<td>{{ $contacts_list->updated_at}}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ $contacts_lists->links('pagination::bootstrap-4') }}
|
||||
|
||||
@endsection
|
||||
|
|
@ -13,6 +13,7 @@
|
|||
<link rel="stylesheet" type="text/css" href="{{ asset('css/' . config('app.env') . '.style.css') }}">
|
||||
@else
|
||||
@endif
|
||||
<script src="{{ asset('scripts/utils.js') }}""></script>
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('css/far.css') }}">
|
||||
<link rel="stylesheet" type="text/css" href="{{ asset('css/form.css') }}">
|
||||
<!--<link rel="stylesheet" type="text/css" href="{{ asset('css/charts.css') }}" >-->
|
||||
|
|
|
|||
|
|
@ -6,6 +6,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'];
|
||||
}
|
||||
@endphp
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ use App\Http\Controllers\Admin\AccountActionController;
|
|||
use App\Http\Controllers\Admin\AccountContactController;
|
||||
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 Illuminate\Support\Facades\Route;
|
||||
|
||||
|
|
@ -127,39 +129,16 @@ if (config('app.web_panel')) {
|
|||
Route::get('auth_tokens/qrcode/{token}', 'Account\AuthTokenController@qrcode')->name('auth_tokens.qrcode');
|
||||
Route::get('auth_tokens/auth/{token}', 'Account\AuthTokenController@auth')->name('auth_tokens.auth');
|
||||
|
||||
//Route::get('admin/accounts/{acc}/cl/{$cl}/detach', 'Admin\AccountController@detachContactsList')->name('admin.account.contacts_lists.detach2');
|
||||
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::prefix('accounts')->group(function () {
|
||||
Route::name('account.type.')->prefix('types')->controller(AccountTypeController::class)->group(function () {
|
||||
Route::get('/', 'index')->name('index');
|
||||
Route::get('create', 'create')->name('create');
|
||||
Route::post('/', 'store')->name('store');
|
||||
Route::get('{type_id}/edit', 'edit')->name('edit');
|
||||
Route::put('{type_id}', 'update')->name('update');
|
||||
Route::get('{type_id}/delete', 'delete')->name('delete');
|
||||
Route::delete('{type_id}', 'destroy')->name('destroy');
|
||||
});
|
||||
|
||||
Route::name('account.account_type.')->prefix('{account}/types')->controller(AccountAccountTypeController::class)->group(function () {
|
||||
Route::get('create', 'create')->name('create');
|
||||
Route::post('/', 'store')->name('store');
|
||||
Route::delete('{type_id}', 'destroy')->name('destroy');
|
||||
});
|
||||
|
||||
Route::name('account.contact.')->prefix('{account}/contacts')->controller(AccountContactController::class)->group(function () {
|
||||
Route::get('create', 'create')->name('create');
|
||||
Route::post('/', 'store')->name('store');
|
||||
Route::get('{contact_id}/delete', 'delete')->name('delete');
|
||||
Route::delete('/', 'destroy')->name('destroy');
|
||||
});
|
||||
|
||||
Route::name('account.')->controller(AdminAccountController::class)->group(function () {
|
||||
Route::get('{account_id}/show', 'show')->name('show');
|
||||
|
||||
Route::name('account.')->prefix('accounts')->group(function () {
|
||||
Route::controller(AdminAccountController::class)->group(function () {
|
||||
Route::get('{account_id}/external_account/attach', 'attachExternalAccount')->name('external_account.attach');
|
||||
|
||||
Route::get('{account_id}/provision', 'provision')->name('provision');
|
||||
|
|
@ -175,9 +154,35 @@ if (config('app.web_panel')) {
|
|||
|
||||
Route::get('/', 'index')->name('index');
|
||||
Route::post('search', 'search')->name('search');
|
||||
|
||||
Route::get('{account_id}/contacts_lists/detach', 'detachContactsList')->name('contacts_lists.detach');
|
||||
Route::post('{account_id}/contacts_lists', 'attachContactsList')->name('contacts_lists.attach');
|
||||
});
|
||||
|
||||
Route::name('account.action.')->prefix('{account}/actions')->controller(AccountActionController::class)->group(function () {
|
||||
Route::name('type.')->prefix('types')->controller(AccountTypeController::class)->group(function () {
|
||||
Route::get('/', 'index')->name('index');
|
||||
Route::get('create', 'create')->name('create');
|
||||
Route::post('/', 'store')->name('store');
|
||||
Route::get('{type_id}/edit', 'edit')->name('edit');
|
||||
Route::put('{type_id}', 'update')->name('update');
|
||||
Route::get('{type_id}/delete', 'delete')->name('delete');
|
||||
Route::delete('{type_id}', 'destroy')->name('destroy');
|
||||
});
|
||||
|
||||
Route::name('account_type.')->prefix('{account}/types')->controller(AccountAccountTypeController::class)->group(function () {
|
||||
Route::get('create', 'create')->name('create');
|
||||
Route::post('/', 'store')->name('store');
|
||||
Route::delete('{type_id}', 'destroy')->name('destroy');
|
||||
});
|
||||
|
||||
Route::name('contact.')->prefix('{account}/contacts')->controller(AccountContactController::class)->group(function () {
|
||||
Route::get('create', 'create')->name('create');
|
||||
Route::post('/', 'store')->name('store');
|
||||
Route::get('{contact_id}/delete', 'delete')->name('delete');
|
||||
Route::delete('/', 'destroy')->name('destroy');
|
||||
});
|
||||
|
||||
Route::name('action.')->prefix('{account}/actions')->controller(AccountActionController::class)->group(function () {
|
||||
Route::get('create', 'create')->name('create');
|
||||
Route::post('/', 'store')->name('store');
|
||||
Route::get('{action_id}/edit', 'edit')->name('edit');
|
||||
|
|
@ -186,5 +191,22 @@ if (config('app.web_panel')) {
|
|||
Route::delete('{action_id}', 'destroy')->name('destroy');
|
||||
});
|
||||
});
|
||||
|
||||
Route::name('contacts_lists.')->prefix('contacts_lists')->controller(ContactsListController::class)->group(function () {
|
||||
Route::get('/', 'index')->name('index');
|
||||
Route::get('create', 'create')->name('create');
|
||||
Route::post('/', 'store')->name('store');
|
||||
Route::get('{contacts_list_id}/edit', 'edit')->name('edit');
|
||||
Route::put('{contacts_list_id}', 'update')->name('update');
|
||||
Route::get('{contacts_list_id}/delete', 'delete')->name('delete');
|
||||
Route::delete('{contacts_list_id}', 'destroy')->name('destroy');
|
||||
|
||||
Route::name('contacts.')->prefix('{contacts_list_id}/contacts')->controller(ContactsListContactController::class)->group(function () {
|
||||
Route::get('add', 'add')->name('add');
|
||||
Route::post('search', 'search')->name('search');
|
||||
Route::post('/', 'store')->name('store');
|
||||
Route::delete('/', 'destroy')->name('destroy');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace Tests\Feature;
|
|||
use App\Password;
|
||||
use App\AccountType;
|
||||
use App\Admin;
|
||||
|
||||
use App\ContactsList;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\TestCase;
|
||||
|
|
@ -48,13 +48,13 @@ class ApiAccountContactTest extends TestCase
|
|||
$admin->account->generateApiKey();
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json($this->method, $this->route.'/'.$password1->account->id.'/contacts/'.$password2->account->id)
|
||||
->json($this->method, $this->route . '/' . $password1->account->id . '/contacts/' . $password2->account->id)
|
||||
->assertStatus(200);
|
||||
|
||||
$this->assertEquals(1, DB::table('contacts')->count());
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json($this->method, $this->route.'/'.$password1->account->id.'/contacts/'.$password3->account->id)
|
||||
->json($this->method, $this->route . '/' . $password1->account->id . '/contacts/' . $password3->account->id)
|
||||
->assertStatus(200);
|
||||
|
||||
$this->assertEquals(2, DB::table('contacts')->count());
|
||||
|
|
@ -69,85 +69,132 @@ class ApiAccountContactTest extends TestCase
|
|||
$accountType = AccountType::first();
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json($this->method, '/api/accounts/'.$password2->account->id.'/types/'.$accountType->id)
|
||||
->json($this->method, '/api/accounts/' . $password2->account->id . '/types/' . $accountType->id)
|
||||
->assertStatus(200);
|
||||
|
||||
// Action
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json($this->method, $this->route.'/'.$password2->account->id.'/actions', [
|
||||
->json($this->method, $this->route . '/' . $password2->account->id . '/actions', [
|
||||
'key' => $actionKey,
|
||||
'code' => $actionCode
|
||||
]);
|
||||
|
||||
// Retry
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->json($this->method, $this->route.'/'.$password1->account->id.'/contacts/'.$password2->account->id)
|
||||
->assertStatus(403);
|
||||
->json($this->method, $this->route . '/' . $password1->account->id . '/contacts/' . $password2->account->id)
|
||||
->assertStatus(403);
|
||||
$this->assertEquals(2, DB::table('contacts')->count());
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get($this->route.'/'.$password1->account->id.'/contacts')
|
||||
->assertJson([
|
||||
->get($this->route . '/' . $password1->account->id . '/contacts')
|
||||
->assertJson([
|
||||
[
|
||||
'id' => $password2->account->id
|
||||
]
|
||||
]);
|
||||
]);
|
||||
|
||||
// /me
|
||||
$password1->account->generateApiKey();
|
||||
$password1->account->save();
|
||||
|
||||
$this->keyAuthenticated($password1->account)
|
||||
->get($this->route.'/me/contacts')
|
||||
->assertStatus(200)
|
||||
->assertJson([[
|
||||
->get($this->route . '/me/contacts')
|
||||
->assertStatus(200)
|
||||
->assertJson([[
|
||||
'username' => $password2->account->username,
|
||||
'activated' => true
|
||||
]]);
|
||||
]]);
|
||||
|
||||
$this->keyAuthenticated($password1->account)
|
||||
->get($this->route.'/me/contacts/'.$password2->account->identifier)
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
->get($this->route . '/me/contacts/' . $password2->account->identifier)
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'username' => $password2->account->username,
|
||||
'activated' => true
|
||||
]);
|
||||
]);
|
||||
|
||||
// Vcard 4.0
|
||||
$this->keyAuthenticated($password1->account)
|
||||
->get('/contacts/vcard')
|
||||
->assertStatus(200)
|
||||
->assertSeeText("FN:".$password2->display_name)
|
||||
->assertSeeText("X-LINPHONE-ACCOUNT-TYPE:".$typeKey)
|
||||
->assertSeeText("X-LINPHONE-ACCOUNT-DTMF-PROTOCOL:".$password2->dtmf_protocol)
|
||||
->assertSeeText("X-LINPHONE-ACCOUNT-ACTION:".$actionKey.';'.$actionCode);
|
||||
->get('/contacts/vcard')
|
||||
->assertStatus(200)
|
||||
->assertSeeText("FN:" . $password2->display_name)
|
||||
->assertSeeText("X-LINPHONE-ACCOUNT-TYPE:" . $typeKey)
|
||||
->assertSeeText("X-LINPHONE-ACCOUNT-DTMF-PROTOCOL:" . $password2->dtmf_protocol)
|
||||
->assertSeeText("X-LINPHONE-ACCOUNT-ACTION:" . $actionKey . ';' . $actionCode);
|
||||
|
||||
$this->keyAuthenticated($password1->account)
|
||||
->get('/contacts/vcard/'.$password2->account->identifier)
|
||||
->assertStatus(200)
|
||||
->assertSeeText("X-LINPHONE-ACCOUNT-TYPE:".$typeKey)
|
||||
->assertSeeText("X-LINPHONE-ACCOUNT-DTMF-PROTOCOL:".$password2->dtmf_protocol)
|
||||
->assertSeeText("X-LINPHONE-ACCOUNT-ACTION:".$actionKey.';'.$actionCode);
|
||||
->get('/contacts/vcard/' . $password2->account->identifier)
|
||||
->assertStatus(200)
|
||||
->assertSeeText("X-LINPHONE-ACCOUNT-TYPE:" . $typeKey)
|
||||
->assertSeeText("X-LINPHONE-ACCOUNT-DTMF-PROTOCOL:" . $password2->dtmf_protocol)
|
||||
->assertSeeText("X-LINPHONE-ACCOUNT-ACTION:" . $actionKey . ';' . $actionCode);
|
||||
|
||||
$this->keyAuthenticated($password1->account)
|
||||
->get($this->route.'/me/contacts/'.$password2->account->identifier)
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
->get($this->route . '/me/contacts/' . $password2->account->identifier)
|
||||
->assertStatus(200)
|
||||
->assertJson([
|
||||
'username' => $password2->account->username,
|
||||
'activated' => true
|
||||
]);
|
||||
]);
|
||||
|
||||
// Remove
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->delete($this->route.'/'.$password1->account->id.'/contacts/'.$password2->account->id)
|
||||
->assertStatus(200);
|
||||
->delete($this->route . '/' . $password1->account->id . '/contacts/' . $password2->account->id)
|
||||
->assertStatus(200);
|
||||
|
||||
$this->assertEquals(1, DB::table('contacts')->count());
|
||||
|
||||
// Retry
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->delete($this->route.'/'.$password1->account->id.'/contacts/'.$password2->account->id)
|
||||
->assertStatus(403);
|
||||
->delete($this->route . '/' . $password1->account->id . '/contacts/' . $password2->account->id)
|
||||
->assertStatus(403);
|
||||
$this->assertEquals(1, DB::table('contacts')->count());
|
||||
|
||||
/**
|
||||
* Contacts lists
|
||||
*
|
||||
*/
|
||||
|
||||
// This will need to be done through the API
|
||||
$contactList = ContactsList::factory()->create();
|
||||
$contactList->contacts()->attach([$password1->account->id, $password2->account->id, $password3->account->id]);
|
||||
|
||||
$admin->account->contactsLists()->attach([$contactList->id]);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get($this->route . '/me/contacts')
|
||||
->assertStatus(200)
|
||||
->assertJsonFragment([
|
||||
'username' => $password1->account->username,
|
||||
'activated' => true
|
||||
])
|
||||
->assertJsonFragment([
|
||||
'username' => $password2->account->username,
|
||||
'activated' => true
|
||||
])
|
||||
->assertJsonFragment([
|
||||
'username' => $password3->account->username,
|
||||
'activated' => true
|
||||
]);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get($this->route . '/me/contacts/' . $password2->account->identifier)
|
||||
->assertStatus(200)
|
||||
->assertJsonFragment([
|
||||
'username' => $password2->account->username,
|
||||
'activated' => true
|
||||
]);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get('/contacts/vcard')
|
||||
->assertStatus(200)
|
||||
->assertSeeText("FN:" . $password1->display_name)
|
||||
->assertSeeText("FN:" . $password2->display_name)
|
||||
->assertSeeText("FN:" . $password3->display_name);
|
||||
|
||||
$this->keyAuthenticated($admin->account)
|
||||
->get('/contacts/vcard/' . $password2->account->identifier)
|
||||
->assertStatus(200)
|
||||
->assertSeeText("FN:" . $password2->display_name);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue