mirror of
https://gitlab.linphone.org/BC/public/flexisip-account-manager.git
synced 2026-01-17 10:08:05 +00:00
Add the Account CSV import feature
This commit is contained in:
parent
4db2b591c5
commit
49d414c9ee
18 changed files with 503 additions and 79 deletions
|
|
@ -41,6 +41,7 @@ class Account extends Authenticatable
|
|||
protected $casts = [
|
||||
'activated' => 'boolean',
|
||||
];
|
||||
protected $fillable = ['username', 'domain', 'email'];
|
||||
|
||||
public static $dtmfProtocols = ['sipinfo' => 'SIPInfo', 'rfc2833' => 'RFC2833', 'sipmessage' => 'SIP Message'];
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class AuthenticateController extends Controller
|
|||
}
|
||||
|
||||
if (!$account) {
|
||||
return redirect()->back()->withErrors(['authentication' => 'The account doesn\'t exists']);
|
||||
return redirect()->back()->withErrors(['authentication' => 'Wrong username or password']);
|
||||
}
|
||||
|
||||
// Try out the passwords
|
||||
|
|
|
|||
|
|
@ -45,9 +45,7 @@ class PasswordController extends Controller
|
|||
$account->activated = true;
|
||||
$account->save();
|
||||
|
||||
$algorithm = $request->has('password_sha256') ? 'SHA-256' : 'MD5';
|
||||
|
||||
$account->updatePassword($request->get('password'), $algorithm);
|
||||
$account->updatePassword($request->get('password'), 'SHA-256');
|
||||
|
||||
if ($account->passwords()->count() > 0) {
|
||||
Log::channel('events')->info('Web: Password changed', ['id' => $account->identifier]);
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ class RecoveryController extends Controller
|
|||
'g-recaptcha-response' => captchaConfigured() ? 'required|captcha' : '',
|
||||
];
|
||||
|
||||
$account = null;
|
||||
|
||||
if ($request->get('email')) {
|
||||
if (config('app.account_email_unique') == false) {
|
||||
$rules['username'] = 'required';
|
||||
|
|
@ -94,12 +96,18 @@ class RecoveryController extends Controller
|
|||
{
|
||||
$request->validate([
|
||||
'account_id' => 'required',
|
||||
'code' => 'required|digits:4'
|
||||
'number_1' => 'required|digits:1',
|
||||
'number_2' => 'required|digits:1',
|
||||
'number_3' => 'required|digits:1',
|
||||
'number_4' => 'required|digits:1'
|
||||
]);
|
||||
|
||||
$code = $request->get('number_1') . $request->get('number_2') . $request->get('number_3') . $request->get('number_4');
|
||||
|
||||
|
||||
$account = Account::where('id', Crypt::decryptString($request->get('account_id')))->firstOrFail();
|
||||
|
||||
if ($account->recovery_code != $request->get('code')) {
|
||||
if ($account->recovery_code != $code) {
|
||||
return redirect()->back()->withErrors([
|
||||
'code' => 'Wrong code'
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -97,6 +97,9 @@ class AccountController extends Controller
|
|||
$account->phone = $request->get('phone');
|
||||
$account->fillPassword($request);
|
||||
|
||||
$account->refresh();
|
||||
$account->setRole($request->get('role'));
|
||||
|
||||
Log::channel('events')->info('Web Admin: Account created', ['id' => $account->identifier]);
|
||||
|
||||
return redirect()->route('admin.account.edit', $account->id);
|
||||
|
|
|
|||
226
flexiapi/app/Http/Controllers/Admin/AccountImportController.php
Normal file
226
flexiapi/app/Http/Controllers/Admin/AccountImportController.php
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Account;
|
||||
use App\Alias;
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Validation\Rules\File;
|
||||
|
||||
class AccountImportController extends Controller
|
||||
{
|
||||
private Collection $errors;
|
||||
private string $importDirectory = 'imported_csv';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->errors = collect();
|
||||
}
|
||||
|
||||
public function create(Request $request)
|
||||
{
|
||||
return view('admin.account.import.create', [
|
||||
'domains' => Account::select('domain')->distinct()->get()->pluck('domain')
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'csv' => ['required', File::types(['csv', 'txt'])],
|
||||
'domain' => 'required|exists:accounts'
|
||||
]);
|
||||
|
||||
$lines = $this->csvToCollection($request->file('csv'));
|
||||
|
||||
/**
|
||||
* Error checking
|
||||
*/
|
||||
|
||||
// Usernames
|
||||
|
||||
$existingUsernames = Account::where('domain', $request->get('domain'))
|
||||
->whereIn('username', $lines->pluck('username')->all())
|
||||
->pluck('username');
|
||||
|
||||
if ($existingUsernames->isNotEmpty()) {
|
||||
$this->errors['Those usernames already exists'] = $existingUsernames->join(', ', ' and ');
|
||||
}
|
||||
|
||||
if ($duplicates = $lines->pluck('username')->duplicates()) {
|
||||
if ($duplicates->isNotEmpty()) {
|
||||
$this->errors['Those usernames are declared several times'] = $duplicates->join(', ', ' and ');
|
||||
}
|
||||
}
|
||||
|
||||
if ($lines->pluck('username')->contains(function ($value) {
|
||||
return strlen($value) < 2;
|
||||
})) {
|
||||
$this->errors['Some usernames are shorter than expected'] = '';
|
||||
}
|
||||
|
||||
// Passwords
|
||||
|
||||
if ($lines->pluck('password')->contains(function ($value) {
|
||||
return strlen($value) < 6;
|
||||
})) {
|
||||
$this->errors['Some passwords are shorter than expected'] = '';
|
||||
}
|
||||
|
||||
// Roles
|
||||
|
||||
if ($lines->pluck('role')->contains(function ($value) {
|
||||
return !in_array($value, ['admin', 'user']);
|
||||
})) {
|
||||
$this->errors['Some roles are not correct'] = '';
|
||||
}
|
||||
|
||||
// Status
|
||||
|
||||
if ($lines->pluck('status')->contains(function ($value) {
|
||||
return !in_array($value, ['active', 'inactive']);
|
||||
})) {
|
||||
$this->errors['Some status are not correct'] = '';
|
||||
}
|
||||
|
||||
// Phones
|
||||
|
||||
if ($phones = $lines->pluck('phone')->filter(function ($value) {
|
||||
return strlen($value) > 2 && substr($value, 0, 1) != '+';
|
||||
})) {
|
||||
if ($phones->isNotEmpty()) {
|
||||
$this->errors['Some phone numbers are not correct'] = $phones->join(', ', ' and ');
|
||||
}
|
||||
}
|
||||
|
||||
$existingPhones = Alias::whereIn('alias', $lines->pluck('phone')->all())
|
||||
->pluck('alias');
|
||||
|
||||
if ($existingPhones->isNotEmpty()) {
|
||||
$this->errors['Those phones numbers already exists'] = $existingPhones->join(', ', ' and ');
|
||||
}
|
||||
|
||||
// Emails
|
||||
|
||||
if ($emails = $lines->pluck('email')->filter(function ($value) {
|
||||
return !filter_var($value, FILTER_VALIDATE_EMAIL);
|
||||
})) {
|
||||
if ($emails->isNotEmpty()) {
|
||||
$this->errors['Some emails are not correct'] = $emails->join(', ', ' and ');
|
||||
}
|
||||
}
|
||||
|
||||
$existingEmails = Account::whereIn('email', $lines->pluck('email')->all())
|
||||
->pluck('email');
|
||||
|
||||
if ($existingEmails->isNotEmpty()) {
|
||||
$this->errors['Those emails numbers already exists'] = $existingEmails->join(', ', ' and ');
|
||||
}
|
||||
|
||||
if ($emails = $lines->pluck('email')->duplicates()) {
|
||||
if ($emails->isNotEmpty()) {
|
||||
$this->errors['Those emails are declared several times'] = $emails->join(', ', ' and ');
|
||||
}
|
||||
}
|
||||
|
||||
$filePath = $this->errors->isEmpty()
|
||||
? Storage::putFile($this->importDirectory, $request->file('csv'))
|
||||
: null;
|
||||
|
||||
return view('admin.account.import.check', [
|
||||
'linesCount' => $lines->count(),
|
||||
'errors' => $this->errors,
|
||||
'domain' => $request->get('domain'),
|
||||
'filePath' => $filePath
|
||||
]);
|
||||
}
|
||||
|
||||
public function handle(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'file_path' => 'required',
|
||||
'domain' => 'required|exists:accounts'
|
||||
]);
|
||||
|
||||
$lines = $this->csvToCollection(storage_path('app/' . $request->get('file_path')));
|
||||
|
||||
$accounts = [];
|
||||
$now = \Carbon\Carbon::now();
|
||||
|
||||
$admins = $phones = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
if ($line->role == 'admin') {
|
||||
array_push($admins, $line->username);
|
||||
}
|
||||
|
||||
if (!empty($line->phone)) {
|
||||
$phones[$line->username] = $line->phone;
|
||||
}
|
||||
|
||||
array_push($accounts, [
|
||||
'username' => $line->username,
|
||||
'domain' => $request->get('domain'),
|
||||
'email' => $line->email,
|
||||
'activated' => $line->status == 'active',
|
||||
'ip_address' => '127.0.0.1',
|
||||
'user_agent' => 'CSV Import',
|
||||
'created_at' => $now,
|
||||
'updated_at' => $now,
|
||||
]);
|
||||
}
|
||||
|
||||
Account::insert($accounts);
|
||||
|
||||
// Set admins accounts
|
||||
foreach ($admins as $username) {
|
||||
$account = Account::where('username', $username)
|
||||
->where('domain', $request->get('domain'))
|
||||
->first();
|
||||
$account->admin = true;
|
||||
}
|
||||
|
||||
// Set admins accounts
|
||||
foreach ($phones as $username => $phone) {
|
||||
$account = Account::where('username', $username)
|
||||
->where('domain', $request->get('domain'))
|
||||
->first();
|
||||
$account->phone = $phone;
|
||||
}
|
||||
|
||||
return redirect()->route('admin.account.index');
|
||||
}
|
||||
|
||||
private function csvToCollection($file): Collection
|
||||
{
|
||||
$lines = collect();
|
||||
$csv = fopen($file, 'r');
|
||||
|
||||
$i = 1;
|
||||
while (!feof($csv)) {
|
||||
if ($line = fgetcsv($csv, 1000, ',')) {
|
||||
$lines->push((object)[
|
||||
'line' => $i,
|
||||
'username' => $line[0],
|
||||
'password' => $line[1],
|
||||
'role' => $line[2],
|
||||
'status' => $line[3],
|
||||
'phone' => $line[4],
|
||||
'email' => $line[5],
|
||||
]);
|
||||
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
fclose($csv);
|
||||
|
||||
$lines->shift();
|
||||
|
||||
return $lines;
|
||||
}
|
||||
}
|
||||
74
flexiapi/public/css/far.css
vendored
74
flexiapi/public/css/far.css
vendored
|
|
@ -106,6 +106,7 @@ body.show_menu {
|
|||
p,
|
||||
a,
|
||||
ul li,
|
||||
ol li,
|
||||
pre {
|
||||
font-size: 1.5rem;
|
||||
color: var(--second-7);
|
||||
|
|
@ -124,11 +125,16 @@ pre code {
|
|||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
ul li {
|
||||
ul li,
|
||||
ol li {
|
||||
margin-left: 2rem;
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
ol li {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
|
||||
ul li ul li {
|
||||
list-style-type: circle;
|
||||
}
|
||||
|
|
@ -279,20 +285,12 @@ header nav a#menu {
|
|||
|
||||
header nav a#menu:after {
|
||||
display: block;
|
||||
font-family: 'Material Icons';
|
||||
font-family: 'Material Icons Outlined';
|
||||
content: "\e5d2";
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
body.show_menu header nav a#menu:after {
|
||||
content: "\e5cd";
|
||||
}
|
||||
|
||||
header nav a#logo span {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
body.show_menu header nav a#menu:after {font-family: 'Material Icons'
|
||||
header nav a#logo {
|
||||
position: absolute;
|
||||
left: calc(50% - 1.5rem);
|
||||
|
|
@ -314,6 +312,7 @@ header nav a#logo::before {
|
|||
display: block;
|
||||
border-radius: 1rem;
|
||||
box-shadow: 0 0 2rem rgba(0, 0, 0, 0.2);
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
header nav a.oppose {
|
||||
|
|
@ -619,7 +618,7 @@ table tr.empty td {
|
|||
|
||||
table tr.empty td:before {
|
||||
content: '\e5c9';
|
||||
font-family: 'Material Icons';
|
||||
font-family: 'Material Icons Outlined';
|
||||
font-size: 8rem;
|
||||
color: var(--second-4);
|
||||
display: block;
|
||||
|
|
@ -753,6 +752,7 @@ select.list_toggle {
|
|||
display: inline-block;
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.breadcrumb li a {
|
||||
|
|
@ -770,3 +770,53 @@ select.list_toggle {
|
|||
line-height: 2rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Steps */
|
||||
|
||||
ol.steps {
|
||||
counter-reset: css-counter 0;
|
||||
padding-top: 6.5rem;
|
||||
}
|
||||
|
||||
ol.steps li {
|
||||
counter-increment: css-counter 1;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
font-weight: 600;
|
||||
color: var(--grey-5);
|
||||
}
|
||||
|
||||
ol.steps li + li {
|
||||
margin-left: 9rem;
|
||||
}
|
||||
|
||||
ol.steps li + li:after {
|
||||
display: block;
|
||||
border-top: 1px solid var(--grey-3);
|
||||
content: "";
|
||||
width: 7rem;
|
||||
position: absolute;
|
||||
top: -3rem;
|
||||
left: -8rem;
|
||||
}
|
||||
|
||||
ol.steps li:before {
|
||||
content: counter(css-counter);
|
||||
display: block;
|
||||
font-size: 2rem;
|
||||
font-weight: 300;
|
||||
border-radius: 3rem;
|
||||
line-height: 5rem;
|
||||
width: 5rem;
|
||||
text-align: center;
|
||||
border: 1px solid var(--grey-3);
|
||||
position: absolute;
|
||||
left: calc(50% - 2.5rem - 1px);
|
||||
bottom: 3rem;
|
||||
}
|
||||
|
||||
ol.steps li.active:before {
|
||||
background-color: var(--main-5);
|
||||
border-color: var(--main-5);
|
||||
color: white;
|
||||
}
|
||||
8
flexiapi/public/css/form.css
vendored
8
flexiapi/public/css/form.css
vendored
|
|
@ -65,16 +65,18 @@ p .btn {
|
|||
|
||||
.btn.btn-tertiary {
|
||||
background-color: var(--main-1);
|
||||
border-color: transparent;
|
||||
border-color: var(--main-1);
|
||||
color: var(--main-5);
|
||||
}
|
||||
|
||||
.btn.btn-tertiary:hover {
|
||||
background-color: var(--main-2);
|
||||
border-color: var(--main-2);
|
||||
}
|
||||
|
||||
.btn.btn-tertiary:active {
|
||||
background-color: var(--main-3);
|
||||
border-color: var(--main-3);
|
||||
}
|
||||
|
||||
form {
|
||||
|
|
@ -129,7 +131,7 @@ form h2 {
|
|||
grid-column: 1/-1;
|
||||
}
|
||||
|
||||
form .disabled {
|
||||
form .disabled:not(a) {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
filter: blur(0.25rem);
|
||||
|
|
@ -205,7 +207,7 @@ form div.checkbox {
|
|||
|
||||
form div.search:after,
|
||||
form div.select:after {
|
||||
font-family: 'Material Icons';
|
||||
font-family: 'Material Icons Outlined';
|
||||
content: "\e5cf";
|
||||
display: block;
|
||||
font-size: 2.5rem;
|
||||
|
|
|
|||
|
|
@ -1,23 +1,36 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item active" aria-current="page">Delete</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<h2>Delete my account</h2>
|
||||
<header>
|
||||
<h1><i class="material-icons-outlined">delete</i> Delete my account</h1>
|
||||
|
||||
<form method="POST" action="{{ route('account.destroy') }}" accept-charset="UTF-8">
|
||||
@csrf
|
||||
<a href="{{ route('account.dashboard') }}" class="btn btn-secondary oppose">Cancel</a>
|
||||
<input form="delete" class="btn" type="submit" value="Delete">
|
||||
</header>
|
||||
|
||||
<form id="delete" method="POST" action="{{ route('account.destroy') }}" accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('delete')
|
||||
|
||||
<div class="large">
|
||||
<p>You are going to permanently delete your account.</p>
|
||||
<p>Please enter your complete username to confirm: <b>{{ $account->identifier }}</b>.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="identifier">Username</label>
|
||||
<input placeholder="bob@example.net" name="identifier" type="text" value="{{ old('identifier') }}">
|
||||
<label for="identifier">Username</label>
|
||||
</div>
|
||||
|
||||
<div class="on_desktop"></div>
|
||||
|
||||
<input name="identifier_confirm" type="hidden" value="{{ $account->identifier }}">
|
||||
|
||||
<input class="btn" type="submit" value="Delete">
|
||||
<div>
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
value="{{ old('username') }}">
|
||||
<label for="username">Username</label>
|
||||
@endif
|
||||
@include('parts.errors', ['name' => 'authentication'])
|
||||
</div>
|
||||
<div class="on_desktop"></div>
|
||||
<div>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,22 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item active" aria-current="page">Change password</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<header>
|
||||
@if ($account->passwords()->count() > 0)
|
||||
<h2>Change my account password</h2>
|
||||
<h1><i class="material-icons-outlined">lock</i> Change password</h1>
|
||||
@else
|
||||
<h2>Set my account password</h2>
|
||||
<h1><i class="material-icons-outlined">lock</i> Set password</h1>
|
||||
@endif
|
||||
|
||||
<form method="POST" action="{{ route('account.password.update') }}" accept-charset="UTF-8">
|
||||
<a href="{{ route('account.dashboard') }}" class="btn btn-secondary oppose">Cancel</a>
|
||||
<input form="password_updated" class="btn" type="submit" value="Change">
|
||||
</header>
|
||||
|
||||
<form id="password_update" method="POST" action="{{ route('account.password.update') }}" accept-charset="UTF-8">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
|
|
@ -18,12 +27,5 @@
|
|||
<input type="password_confirmation" name="password_confirmation" required>
|
||||
<label for="password_confirmation">Password confirmation</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="checkbox" name="password_sha256" checked>
|
||||
<label for="password_sha256">Use a SHA-256 encrypted password. This stronger password might not work with some
|
||||
old SIP clients</label>
|
||||
</div>
|
||||
|
||||
<input class="btn btn-primary" type="submit" value="Change">
|
||||
</form>
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -9,8 +9,12 @@
|
|||
|
||||
<p class="large">Enter the pin code you received to recover your account.</p>
|
||||
<div class="large">
|
||||
<input placeholder="1234" name="code" type="text" value="{{ old('code') }}">
|
||||
<label for="code">Code</label>
|
||||
<input oninput="digitFilled(this)" onfocus="this.value = ''" autofocus class="digit" name="number_1" type="number" min="0" max="9">
|
||||
<input oninput="digitFilled(this)" onfocus="this.value = ''" class="digit" name="number_2" type="number" min="0" max="9">
|
||||
<input oninput="digitFilled(this)" onfocus="this.value = ''" class="digit" name="number_3" type="number" min="0" max="9">
|
||||
<input oninput="digitFilled(this)" onfocus="this.value = ''" class="digit" name="number_4" type="number" min="0" max="9">
|
||||
@include('parts.errors', ['name' => 'code'])
|
||||
|
||||
<input name="account_id" type="hidden" value="{{ $account_id }}">
|
||||
</div>
|
||||
<div class="large">
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
<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') }}
|
||||
|
||||
@include('parts.tabs', [
|
||||
'items' => [
|
||||
route('admin.account.edit', $account->id, ['type' => 'messages']) => 'Information',
|
||||
|
|
@ -42,7 +41,7 @@
|
|||
<h2>Connexion</h2>
|
||||
<div>
|
||||
<input placeholder="Username" required="required" name="username" type="text"
|
||||
value="{{ $account->username }}" @if ($account->id) readonly @endif>
|
||||
value="@if ($account->id){{ $account->username }}@else{{ old('username') }}@endif" @if ($account->id) readonly @endif>
|
||||
<label for="username">Username</label>
|
||||
@include('parts.errors', ['name' => 'username'])
|
||||
</div>
|
||||
|
|
@ -54,32 +53,32 @@
|
|||
</div>
|
||||
|
||||
<div>
|
||||
<input placeholder="John Doe" name="display_name" type="text" value="{{ $account->display_name }}">
|
||||
<input placeholder="John Doe" name="display_name" type="text" value="@if ($account->id){{ $account->display_name }}@else{{ old('display_name') }}@endif">
|
||||
<label for="display_name">Display Name</label>
|
||||
@include('parts.errors', ['name' => 'display_name'])
|
||||
</div>
|
||||
<div></div>
|
||||
|
||||
<div>
|
||||
<input placeholder="Password" name="password" type="password" value="" autocomplete="new-password">
|
||||
<input placeholder="Password" name="password" type="password" value="" autocomplete="new-password" @if (!$account->id)required @endif>
|
||||
<label for="password">{{ $account->id ? 'Password (fill to change)' : 'Password' }}</label>
|
||||
@include('parts.errors', ['name' => 'password'])
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input placeholder="Password" name="password_confirmation" type="password" value="" autocomplete="off">
|
||||
<input placeholder="Password" name="password_confirmation" type="password" value="" autocomplete="off" @if (!$account->id)required @endif>
|
||||
<label for="password_confirmation">Confirm password</label>
|
||||
@include('parts.errors', ['name' => 'password_confirmation'])
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input placeholder="Email" name="email" type="email" value="{{ $account->email }}">
|
||||
<input placeholder="Email" name="email" type="email" value="@if ($account->id){{ $account->email }}@else{{ old('email') }}@endif">
|
||||
<label for="email">Email</label>
|
||||
@include('parts.errors', ['name' => 'email'])
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<input placeholder="+12123123" name="phone" type="text" value="{{ $account->phone }}">
|
||||
<input placeholder="+12123123" name="phone" type="text" value="@if ($account->id){{ $account->phone }}@else{{ old('phone') }}@endif">
|
||||
<label for="phone">Phone</label>
|
||||
@include('parts.errors', ['name' => 'phone'])
|
||||
</div>
|
||||
|
|
@ -242,9 +241,11 @@
|
|||
<tr>
|
||||
<th scope="row">{{ $type->key }}</th>
|
||||
<td>
|
||||
<form method="POST" action="{{ route('admin.account.account_type.destroy', [$account, $type->id]) }}" accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('delete')
|
||||
<form method="POST"
|
||||
action="{{ route('admin.account.account_type.destroy', [$account, $type->id]) }}"
|
||||
accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('delete')
|
||||
<input class="btn" type="submit" value="Delete">
|
||||
</form>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -8,24 +8,23 @@
|
|||
@endsection
|
||||
|
||||
@section('content')
|
||||
<h2>Delete an account</h2>
|
||||
|
||||
<form method="POST" action="{{ route('admin.account.destroy') }}" accept-charset="UTF-8">
|
||||
@csrf
|
||||
<header>
|
||||
<h1><i class="material-icons-outlined">delete</i> Delete an account</h1>
|
||||
|
||||
<a href="{{ route('admin.account.edit', $account->id) }}" class="btn btn-secondary oppose">Cancel</a>
|
||||
<input form="delete" class="btn" type="submit" value="Delete">
|
||||
</header>
|
||||
<form id="delete" method="POST" action="{{ route('admin.account.destroy') }}" accept-charset="UTF-8">
|
||||
@csrf
|
||||
@method('delete')
|
||||
|
||||
<div class="large">
|
||||
<p>You are going to permanently delete the following account. Please confirm your action.<br />
|
||||
<b>{{ $account->identifier }}</b>
|
||||
</p>
|
||||
|
||||
<input name="account_id" type="hidden" value="{{ $account->id }}">
|
||||
</div>
|
||||
<div>
|
||||
<input class="btn" type="submit" value="Delete">
|
||||
|
||||
</div>
|
||||
|
||||
</form>
|
||||
@endsection
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{{ route('admin.account.index') }}">Accounts</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Import</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<header>
|
||||
<h1><i class="material-icons-outlined">people</i> Import accounts</h1>
|
||||
<a href="{{ route('admin.account.index') }}" class="btn btn-secondary oppose">Cancel</a>
|
||||
|
||||
<a href="#" onclick="history.back()" class="btn btn-secondary">Previous</a>
|
||||
<form name="handle" method="POST" action="{{ route('admin.account.import.handle') }}" accept-charset="UTF-8"
|
||||
enctype="multipart/form-data">
|
||||
@csrf
|
||||
<input name="file_path" type="hidden" value="{{ $filePath }}">
|
||||
<input name="domain" type="hidden" value="{{ $domain }}">
|
||||
<a type="submit"
|
||||
class="btn @if ($errors->isNotEmpty()) disabled @endif" onclick="document.querySelector('form[name=handle]').submit()">
|
||||
<i class="material-icons-outlined">publish</i>
|
||||
Import
|
||||
</a>
|
||||
</form>
|
||||
</header>
|
||||
|
||||
<div>
|
||||
<ol class="steps" style="margin: 6rem 0;">
|
||||
<li>Select data file</li>
|
||||
<li class="active">Import data</li>
|
||||
</ol>
|
||||
|
||||
<h3>{{ $linesCount }} accounts will be imported for the {{ $domain }} domain</h3>
|
||||
|
||||
@if ($errors->isNotEmpty())
|
||||
<hr />
|
||||
|
||||
<h3>Errors</h3>
|
||||
|
||||
@foreach ($errors as $title => $body)
|
||||
<p><b>{{ $title }}</b> {{ $body }}</p>
|
||||
@endforeach
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
@extends('layouts.main')
|
||||
|
||||
@section('breadcrumb')
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{{ route('admin.account.index') }}">Accounts</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Import</li>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<header>
|
||||
<h1><i class="material-icons-outlined">people</i> Import accounts</h1>
|
||||
<a href="{{ route('admin.account.index') }}" class="btn btn-secondary oppose">Cancel</a>
|
||||
<input form="import" class="btn" type="submit" value="Next">
|
||||
</header>
|
||||
|
||||
<div>
|
||||
<ol class="steps" style="margin: 6rem 0;">
|
||||
<li class="active">Select data file</li>
|
||||
<li>Import data</li>
|
||||
</ol>
|
||||
|
||||
<p>Use this existing (.csv) template or create your own csv file.</p>
|
||||
|
||||
<p>
|
||||
This file MUST be in csv format and contain at least the following information:
|
||||
</p>
|
||||
<ol>
|
||||
<li>The first line contains the labels</li>
|
||||
<li>Username* </li>
|
||||
<li>Password* (6 characters minimum)</li>
|
||||
<li>Role* (admin or user)</li>
|
||||
<li>Statuts* (active, inactive)</li>
|
||||
<li>Phone number, must start with a + if set</li>
|
||||
</ol>
|
||||
|
||||
<hr />
|
||||
|
||||
<form id="import" method="POST" action="{{ route('admin.account.import.store') }}" accept-charset="UTF-8" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div>
|
||||
<input name="csv" type="file" accept=".csv">
|
||||
@include('parts.errors', ['name' => 'csv'])
|
||||
<label for="csv">CSV file to import</label>
|
||||
</div>
|
||||
<div class="on_desktop"></div>
|
||||
|
||||
<div class="select">
|
||||
<select name="domain">
|
||||
@foreach ($domains as $domain)
|
||||
<option value="{{ $domain }}">{{ $domain }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<label for="domain">Domain used for the import</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
|
|
@ -9,7 +9,11 @@
|
|||
@section('content')
|
||||
<header>
|
||||
<h1><i class="material-icons-outlined">people</i> Accounts</h1>
|
||||
<a class="btn oppose" href="{{ route('admin.account.create') }}">
|
||||
<a class="btn btn-secondary oppose" href="{{ route('admin.account.import.create') }}">
|
||||
<i class="material-icons-outlined">publish</i>
|
||||
Import Accounts
|
||||
</a>
|
||||
<a class="btn" href="{{ route('admin.account.create') }}">
|
||||
<i class="material-icons-outlined">add_circle</i>
|
||||
New Account
|
||||
</a>
|
||||
|
|
@ -75,7 +79,7 @@
|
|||
<tbody>
|
||||
@if ($accounts->isEmpty())
|
||||
<tr class="empty">
|
||||
<td colspan="4">No Contacts Lists</td>
|
||||
<td colspan="4">No Accounts</td>
|
||||
</tr>
|
||||
@endif
|
||||
@foreach ($accounts as $account)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use App\Http\Controllers\Admin\AccountContactController;
|
|||
use App\Http\Controllers\Admin\AccountDeviceController;
|
||||
use App\Http\Controllers\Admin\AccountTypeController;
|
||||
use App\Http\Controllers\Admin\AccountController as AdminAccountController;
|
||||
use App\Http\Controllers\Admin\AccountImportController;
|
||||
use App\Http\Controllers\Admin\ContactsListController;
|
||||
use App\Http\Controllers\Admin\ContactsListContactController;
|
||||
use App\Http\Controllers\Admin\StatisticsController;
|
||||
|
|
@ -153,6 +154,12 @@ if (config('app.web_panel')) {
|
|||
Route::post('{account_id}/contacts_lists', 'contactsListAdd')->name('contacts_lists.attach');
|
||||
});
|
||||
|
||||
Route::name('import.')->prefix('import')->controller(AccountImportController::class)->group(function () {
|
||||
Route::get('/', 'create')->name('create');
|
||||
Route::post('/', 'store')->name('store');
|
||||
Route::post('handle', 'handle')->name('handle');
|
||||
});
|
||||
|
||||
Route::name('type.')->prefix('types')->controller(AccountTypeController::class)->group(function () {
|
||||
Route::get('/', 'index')->name('index');
|
||||
Route::get('create', 'create')->name('create');
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue