Updated to vue 3
This commit is contained in:
parent
37d6af9719
commit
7d3219cdb4
43 changed files with 2061 additions and 1678 deletions
|
@ -462,7 +462,7 @@ For full details please see the [self-hosting instructions file](SELF-HOSTING.md
|
||||||
|
|
||||||
## My sponsors
|
## My sponsors
|
||||||
|
|
||||||
Thanks to [Vlad Timofeev](https://github.com/vlad-timofeev), [Patrick Dobler](https://github.com/patrickdobler) and [Luca Steeb](https://github.com/steebchen) for supporting me by sponsoring the project on GitHub!
|
Thanks to [Vlad Timofeev](https://github.com/vlad-timofeev), [Patrick Dobler](https://github.com/patrickdobler), [Luca Steeb](https://github.com/steebchen) and [Laiteux](https://github.com/Laiteux) for supporting me by sponsoring the project on GitHub!
|
||||||
|
|
||||||
Also an extra special thanks to [CrazyMax](https://github.com/crazy-max) for sponsoring me and also creating and maintaining the awesome [AnonAddy Docker image](https://github.com/anonaddy/docker)!
|
Also an extra special thanks to [CrazyMax](https://github.com/crazy-max) for sponsoring me and also creating and maintaining the awesome [AnonAddy Docker image](https://github.com/anonaddy/docker)!
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,6 @@ class ReceiveEmail extends Command
|
||||||
$this->size = $this->option('size') / ($recipientCount ? $recipientCount : 1);
|
$this->size = $this->option('size') / ($recipientCount ? $recipientCount : 1);
|
||||||
|
|
||||||
foreach ($recipients as $recipient) {
|
foreach ($recipients as $recipient) {
|
||||||
|
|
||||||
// Handle bounces
|
// Handle bounces
|
||||||
if ($this->option('sender') === 'MAILER-DAEMON') {
|
if ($this->option('sender') === 'MAILER-DAEMON') {
|
||||||
$this->handleBounce($recipient['email']);
|
$this->handleBounce($recipient['email']);
|
||||||
|
@ -145,7 +144,6 @@ class ReceiveEmail extends Command
|
||||||
$verifiedRecipient = $user->getVerifiedRecipientByEmail($this->senderFrom);
|
$verifiedRecipient = $user->getVerifiedRecipientByEmail($this->senderFrom);
|
||||||
|
|
||||||
if ($validEmailDestination && $verifiedRecipient?->can_reply_send) {
|
if ($validEmailDestination && $verifiedRecipient?->can_reply_send) {
|
||||||
|
|
||||||
// Check if the Dmarc allow or spam headers are present from Rspamd
|
// Check if the Dmarc allow or spam headers are present from Rspamd
|
||||||
if (! $this->parser->getHeader('X-AnonAddy-Dmarc-Allow') || $this->parser->getHeader('X-AnonAddy-Spam')) {
|
if (! $this->parser->getHeader('X-AnonAddy-Dmarc-Allow') || $this->parser->getHeader('X-AnonAddy-Spam')) {
|
||||||
// Notify user and exit
|
// Notify user and exit
|
||||||
|
@ -295,7 +293,6 @@ class ReceiveEmail extends Command
|
||||||
|
|
||||||
// Verify queue ID
|
// Verify queue ID
|
||||||
if (isset($dsn['X-postfix-queue-id'])) {
|
if (isset($dsn['X-postfix-queue-id'])) {
|
||||||
|
|
||||||
// First check in DB
|
// First check in DB
|
||||||
$postfixQueueId = PostfixQueueId::firstWhere('queue_id', strtoupper($dsn['X-postfix-queue-id']));
|
$postfixQueueId = PostfixQueueId::firstWhere('queue_id', strtoupper($dsn['X-postfix-queue-id']));
|
||||||
|
|
||||||
|
|
24
app/Http/Controllers/Api/ApiTokenDetailController.php
Normal file
24
app/Http/Controllers/Api/ApiTokenDetailController.php
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class ApiTokenDetailController extends Controller
|
||||||
|
{
|
||||||
|
public function show(Request $request)
|
||||||
|
{
|
||||||
|
$token = $request->user()->currentAccessToken();
|
||||||
|
|
||||||
|
if (! $token) {
|
||||||
|
return response('Current token could not be found', 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'name' => $token->name,
|
||||||
|
'created_at' => $token->created_at?->toDateTimeString(),
|
||||||
|
'expires_at' => $token->expires_at?->toDateTimeString()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,6 +43,8 @@ class RecipientKeyController extends Controller
|
||||||
'should_encrypt' => false,
|
'should_encrypt' => false,
|
||||||
'inline_encryption' => false,
|
'inline_encryption' => false,
|
||||||
'protected_headers' => false,
|
'protected_headers' => false,
|
||||||
|
'inline_encryption' => false,
|
||||||
|
'protected_headers' => false,
|
||||||
'fingerprint' => null
|
'fingerprint' => null
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
100
app/Http/Controllers/Auth/ApiAuthenticationController.php
Normal file
100
app/Http/Controllers/Auth/ApiAuthenticationController.php
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Facades\Webauthn;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Http\Requests\ApiAuthenticationLoginRequest;
|
||||||
|
use App\Http\Requests\ApiAuthenticationMfaRequest;
|
||||||
|
use App\Models\User;
|
||||||
|
use App\Models\Username;
|
||||||
|
use Illuminate\Contracts\Encryption\DecryptException;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Illuminate\Support\Facades\Crypt;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use PragmaRX\Google2FA\Google2FA;
|
||||||
|
|
||||||
|
class ApiAuthenticationController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->middleware('throttle:3,1');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function login(ApiAuthenticationLoginRequest $request)
|
||||||
|
{
|
||||||
|
$user = Username::firstWhere('username', $request->username)?->user;
|
||||||
|
|
||||||
|
if (! $user || ! Hash::check($request->password, $user->password)) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'The provided credentials are incorrect'
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has 2FA enabled, if needs OTP then return mfa_key
|
||||||
|
if ($user->two_factor_enabled) {
|
||||||
|
return response()->json([
|
||||||
|
'message' => "OTP required, please make a request to /api/auth/mfa with the 'mfa_key', 'otp' and 'device_name' including a 'X-CSRF-TOKEN' header",
|
||||||
|
'mfa_key' => Crypt::encryptString($user->id.'|'.config('anonaddy.secret').'|'.Carbon::now()->addMinutes(5)->getTimestamp()),
|
||||||
|
'csrf_token' => csrf_token()
|
||||||
|
], 422);
|
||||||
|
} elseif (Webauthn::enabled($user)) {
|
||||||
|
// If WebAuthn is enabled then return currently unsupported message
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'WebAuthn authentication is not currently supported from the extension or mobile apps, please use an API key to login instead'
|
||||||
|
], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the user doesn't use 2FA then return the new API key
|
||||||
|
return response()->json([
|
||||||
|
'api_key' => explode('|', $user->createToken($request->device_name)->plainTextToken, 2)[1]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function mfa(ApiAuthenticationMfaRequest $request)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$mfaKey = Crypt::decryptString($request->mfa_key);
|
||||||
|
} catch (DecryptException $e) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'Invalid mfa_key'
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
$parts = explode('|', $mfaKey, 3);
|
||||||
|
|
||||||
|
$user = User::find($parts[0]);
|
||||||
|
|
||||||
|
if (! $user || $parts[1] !== config('anonaddy.secret')) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'Invalid mfa_key'
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the mfa_key has expired
|
||||||
|
if (Carbon::now()->getTimestamp() > $parts[2]) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'mfa_key expired, please request a new one at /api/auth/login'
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
$google2fa = new Google2FA();
|
||||||
|
$lastTimeStamp = Cache::get('2fa_ts:'.$user->id);
|
||||||
|
|
||||||
|
$timestamp = $google2fa->verifyKeyNewer($user->two_factor_secret, $request->otp, $lastTimeStamp);
|
||||||
|
|
||||||
|
if (! $timestamp) {
|
||||||
|
return response()->json([
|
||||||
|
'error' => 'The \'One Time Password\' typed was wrong'
|
||||||
|
], 401);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_int($timestamp)) {
|
||||||
|
Cache::put('2fa_ts:'.$user->id, $timestamp, now()->addMinutes(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'api_key' => explode('|', $user->createToken($request->device_name)->plainTextToken, 2)[1]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,7 +39,7 @@ class ForgotUsernameController extends Controller
|
||||||
{
|
{
|
||||||
$this->validateEmail($request);
|
$this->validateEmail($request);
|
||||||
|
|
||||||
$recipient = Recipient::select(['id', 'email', 'email_verified_at'])->whereNotNull('email_verified_at')->get()->firstWhere('email', strtolower($request->email));
|
$recipient = Recipient::select(['id', 'user_id', 'email', 'should_encrypt', 'fingerprint', 'email_verified_at'])->whereNotNull('email_verified_at')->get()->firstWhere('email', strtolower($request->email));
|
||||||
|
|
||||||
if (isset($recipient)) {
|
if (isset($recipient)) {
|
||||||
$recipient->sendUsernameReminderNotification();
|
$recipient->sendUsernameReminderNotification();
|
||||||
|
|
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers\Auth;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Http\Requests\StorePersonalAccessTokenRequest;
|
use App\Http\Requests\StorePersonalAccessTokenRequest;
|
||||||
use App\Http\Resources\PersonalAccessTokenResource;
|
use App\Http\Resources\PersonalAccessTokenResource;
|
||||||
|
use chillerlan\QRCode\QRCode;
|
||||||
|
|
||||||
class PersonalAccessTokenController extends Controller
|
class PersonalAccessTokenController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -24,10 +25,12 @@ class PersonalAccessTokenController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
$token = user()->createToken($request->name, ['*'], $expiration);
|
$token = user()->createToken($request->name, ['*'], $expiration);
|
||||||
|
$accessToken = explode('|', $token->plainTextToken, 2)[1];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'token' => new PersonalAccessTokenResource($token->accessToken),
|
'token' => new PersonalAccessTokenResource($token->accessToken),
|
||||||
'accessToken' => explode('|', $token->plainTextToken, 2)[1]
|
'accessToken' => $accessToken,
|
||||||
|
'qrCode' => (new QRCode())->render(config('app.url') . "|" . $accessToken)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ class Kernel extends HttpKernel
|
||||||
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
|
||||||
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
|
||||||
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
|
||||||
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
|
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
|
||||||
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
|
||||||
'2fa' => \App\Http\Middleware\VerifyTwoFactorAuth::class,
|
'2fa' => \App\Http\Middleware\VerifyTwoFactorAuth::class,
|
||||||
'webauthn' => \App\Http\Middleware\VerifyWebauthn::class,
|
'webauthn' => \App\Http\Middleware\VerifyWebauthn::class,
|
||||||
|
|
|
@ -19,6 +19,6 @@ class VerifyCsrfToken extends Middleware
|
||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $except = [
|
protected $except = [
|
||||||
//
|
'api/auth/login'
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
32
app/Http/Requests/ApiAuthenticationLoginRequest.php
Normal file
32
app/Http/Requests/ApiAuthenticationLoginRequest.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ApiAuthenticationLoginRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'username' => 'required|string',
|
||||||
|
'password' => 'required|string',
|
||||||
|
'device_name' => 'required|string|max:50'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
32
app/Http/Requests/ApiAuthenticationMfaRequest.php
Normal file
32
app/Http/Requests/ApiAuthenticationMfaRequest.php
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class ApiAuthenticationMfaRequest extends FormRequest
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Determine if the user is authorized to make this request.
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function authorize()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the validation rules that apply to the request.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function rules()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'mfa_key' => 'required|string|max:500',
|
||||||
|
'otp' => 'required|string|min:6|max:6',
|
||||||
|
'device_name' => 'required|string|max:50'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,7 @@
|
||||||
"php": "^8.0.2",
|
"php": "^8.0.2",
|
||||||
"asbiin/laravel-webauthn": "^3.0.0",
|
"asbiin/laravel-webauthn": "^3.0.0",
|
||||||
"bacon/bacon-qr-code": "^2.0",
|
"bacon/bacon-qr-code": "^2.0",
|
||||||
|
"chillerlan/php-qrcode": "^4.3",
|
||||||
"doctrine/dbal": "^3.0",
|
"doctrine/dbal": "^3.0",
|
||||||
"guzzlehttp/guzzle": "^7.2",
|
"guzzlehttp/guzzle": "^7.2",
|
||||||
"laravel/framework": "^9.11",
|
"laravel/framework": "^9.11",
|
||||||
|
|
547
composer.lock
generated
547
composer.lock
generated
|
@ -4,20 +4,20 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "e82a971bfd5ab4ba0fa31965a1c6ca60",
|
"content-hash": "6a781a6eb7831ca8568ca458163ccb56",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "asbiin/laravel-webauthn",
|
"name": "asbiin/laravel-webauthn",
|
||||||
"version": "3.2.0",
|
"version": "3.2.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/asbiin/laravel-webauthn.git",
|
"url": "https://github.com/asbiin/laravel-webauthn.git",
|
||||||
"reference": "747df5ab7bf0f88bbb36f11c0f281464d6c29d0b"
|
"reference": "05610549427974bf8a3f0cc32a58e4050d9f7792"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/asbiin/laravel-webauthn/zipball/747df5ab7bf0f88bbb36f11c0f281464d6c29d0b",
|
"url": "https://api.github.com/repos/asbiin/laravel-webauthn/zipball/05610549427974bf8a3f0cc32a58e4050d9f7792",
|
||||||
"reference": "747df5ab7bf0f88bbb36f11c0f281464d6c29d0b",
|
"reference": "05610549427974bf8a3f0cc32a58e4050d9f7792",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -92,7 +92,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-07-30T09:08:07+00:00"
|
"time": "2022-08-20T17:56:48+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "bacon/bacon-qr-code",
|
"name": "bacon/bacon-qr-code",
|
||||||
|
@ -217,16 +217,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
"version": "0.10.1",
|
"version": "0.10.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/brick/math.git",
|
"url": "https://github.com/brick/math.git",
|
||||||
"reference": "de846578401f4e58f911b3afeb62ced56365ed87"
|
"reference": "459f2781e1a08d52ee56b0b1444086e038561e3f"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/brick/math/zipball/de846578401f4e58f911b3afeb62ced56365ed87",
|
"url": "https://api.github.com/repos/brick/math/zipball/459f2781e1a08d52ee56b0b1444086e038561e3f",
|
||||||
"reference": "de846578401f4e58f911b3afeb62ced56365ed87",
|
"reference": "459f2781e1a08d52ee56b0b1444086e038561e3f",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -261,7 +261,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/brick/math/issues",
|
"issues": "https://github.com/brick/math/issues",
|
||||||
"source": "https://github.com/brick/math/tree/0.10.1"
|
"source": "https://github.com/brick/math/tree/0.10.2"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -269,7 +269,149 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-08-01T22:54:31+00:00"
|
"time": "2022-08-10T22:54:19+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chillerlan/php-qrcode",
|
||||||
|
"version": "4.3.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/chillerlan/php-qrcode.git",
|
||||||
|
"reference": "2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/chillerlan/php-qrcode/zipball/2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d",
|
||||||
|
"reference": "2ca4bf5ae048af1981d1023ee42a0a2a9d51e51d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"chillerlan/php-settings-container": "^2.1.4",
|
||||||
|
"ext-mbstring": "*",
|
||||||
|
"php": "^7.4 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phan/phan": "^5.3",
|
||||||
|
"phpunit/phpunit": "^9.5",
|
||||||
|
"setasign/fpdf": "^1.8.2"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"chillerlan/php-authenticator": "Yet another Google authenticator! Also creates URIs for mobile apps.",
|
||||||
|
"setasign/fpdf": "Required to use the QR FPDF output."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"chillerlan\\QRCode\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Kazuhiko Arase",
|
||||||
|
"homepage": "https://github.com/kazuhikoarase"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Smiley",
|
||||||
|
"email": "smiley@chillerlan.net",
|
||||||
|
"homepage": "https://github.com/codemasher"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Contributors",
|
||||||
|
"homepage": "https://github.com/chillerlan/php-qrcode/graphs/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A QR code generator. PHP 7.4+",
|
||||||
|
"homepage": "https://github.com/chillerlan/php-qrcode",
|
||||||
|
"keywords": [
|
||||||
|
"phpqrcode",
|
||||||
|
"qr",
|
||||||
|
"qr code",
|
||||||
|
"qrcode",
|
||||||
|
"qrcode-generator"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/chillerlan/php-qrcode/issues",
|
||||||
|
"source": "https://github.com/chillerlan/php-qrcode/tree/4.3.4"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://ko-fi.com/codemasher",
|
||||||
|
"type": "ko_fi"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2022-07-25T09:12:45+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chillerlan/php-settings-container",
|
||||||
|
"version": "2.1.4",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/chillerlan/php-settings-container.git",
|
||||||
|
"reference": "1beb7df3c14346d4344b0b2e12f6f9a74feabd4a"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/chillerlan/php-settings-container/zipball/1beb7df3c14346d4344b0b2e12f6f9a74feabd4a",
|
||||||
|
"reference": "1beb7df3c14346d4344b0b2e12f6f9a74feabd4a",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"php": "^7.4 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phan/phan": "^5.3",
|
||||||
|
"phpunit/phpunit": "^9.5"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"chillerlan\\Settings\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Smiley",
|
||||||
|
"email": "smiley@chillerlan.net",
|
||||||
|
"homepage": "https://github.com/codemasher"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A container class for immutable settings objects. Not a DI container. PHP 7.4+",
|
||||||
|
"homepage": "https://github.com/chillerlan/php-settings-container",
|
||||||
|
"keywords": [
|
||||||
|
"PHP7",
|
||||||
|
"Settings",
|
||||||
|
"configuration",
|
||||||
|
"container",
|
||||||
|
"helper"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/chillerlan/php-settings-container/issues",
|
||||||
|
"source": "https://github.com/chillerlan/php-settings-container"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://www.paypal.com/donate?hosted_button_id=WLYUNAT9ZTJZ4",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://ko-fi.com/codemasher",
|
||||||
|
"type": "ko_fi"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2022-07-05T22:32:14+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dasprid/enum",
|
"name": "dasprid/enum",
|
||||||
|
@ -488,16 +630,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/dbal",
|
"name": "doctrine/dbal",
|
||||||
"version": "3.4.0",
|
"version": "3.4.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/doctrine/dbal.git",
|
"url": "https://github.com/doctrine/dbal.git",
|
||||||
"reference": "118a360e9437e88d49024f36283c8bcbd76105f5"
|
"reference": "22de295f10edbe00df74f517612f1fbd711131e2"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/118a360e9437e88d49024f36283c8bcbd76105f5",
|
"url": "https://api.github.com/repos/doctrine/dbal/zipball/22de295f10edbe00df74f517612f1fbd711131e2",
|
||||||
"reference": "118a360e9437e88d49024f36283c8bcbd76105f5",
|
"reference": "22de295f10edbe00df74f517612f1fbd711131e2",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -579,7 +721,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/doctrine/dbal/issues",
|
"issues": "https://github.com/doctrine/dbal/issues",
|
||||||
"source": "https://github.com/doctrine/dbal/tree/3.4.0"
|
"source": "https://github.com/doctrine/dbal/tree/3.4.2"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -595,7 +737,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-08-06T20:35:57+00:00"
|
"time": "2022-08-21T14:21:06+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/deprecations",
|
"name": "doctrine/deprecations",
|
||||||
|
@ -1695,16 +1837,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/framework",
|
"name": "laravel/framework",
|
||||||
"version": "v9.24.0",
|
"version": "v9.25.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/laravel/framework.git",
|
"url": "https://github.com/laravel/framework.git",
|
||||||
"reference": "053840f579cf01d353d81333802afced79b1c0af"
|
"reference": "e8af8c2212e3717757ea7f459a655a2e9e771109"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/laravel/framework/zipball/053840f579cf01d353d81333802afced79b1c0af",
|
"url": "https://api.github.com/repos/laravel/framework/zipball/e8af8c2212e3717757ea7f459a655a2e9e771109",
|
||||||
"reference": "053840f579cf01d353d81333802afced79b1c0af",
|
"reference": "e8af8c2212e3717757ea7f459a655a2e9e771109",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -1871,7 +2013,7 @@
|
||||||
"issues": "https://github.com/laravel/framework/issues",
|
"issues": "https://github.com/laravel/framework/issues",
|
||||||
"source": "https://github.com/laravel/framework"
|
"source": "https://github.com/laravel/framework"
|
||||||
},
|
},
|
||||||
"time": "2022-08-09T13:43:22+00:00"
|
"time": "2022-08-16T16:36:05+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/sanctum",
|
"name": "laravel/sanctum",
|
||||||
|
@ -2316,16 +2458,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "league/flysystem",
|
"name": "league/flysystem",
|
||||||
"version": "3.2.0",
|
"version": "3.2.1",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/thephpleague/flysystem.git",
|
"url": "https://github.com/thephpleague/flysystem.git",
|
||||||
"reference": "ed0ecc7f9b5c2f4a9872185846974a808a3b052a"
|
"reference": "81aea9e5217084c7850cd36e1587ee4aad721c6b"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/ed0ecc7f9b5c2f4a9872185846974a808a3b052a",
|
"url": "https://api.github.com/repos/thephpleague/flysystem/zipball/81aea9e5217084c7850cd36e1587ee4aad721c6b",
|
||||||
"reference": "ed0ecc7f9b5c2f4a9872185846974a808a3b052a",
|
"reference": "81aea9e5217084c7850cd36e1587ee4aad721c6b",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -2386,7 +2528,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/thephpleague/flysystem/issues",
|
"issues": "https://github.com/thephpleague/flysystem/issues",
|
||||||
"source": "https://github.com/thephpleague/flysystem/tree/3.2.0"
|
"source": "https://github.com/thephpleague/flysystem/tree/3.2.1"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -2402,7 +2544,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-07-26T07:26:36+00:00"
|
"time": "2022-08-14T20:48:34+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "league/mime-type-detection",
|
"name": "league/mime-type-detection",
|
||||||
|
@ -7269,16 +7411,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "web-auth/cose-lib",
|
"name": "web-auth/cose-lib",
|
||||||
"version": "v4.0.5",
|
"version": "v4.0.6",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/web-auth/cose-lib.git",
|
"url": "https://github.com/web-auth/cose-lib.git",
|
||||||
"reference": "2fe6c0d35136d75bc538372a317ca5df5a75ce73"
|
"reference": "d72032843c97ba40edb682dfcec0b787b25cbe6f"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/web-auth/cose-lib/zipball/2fe6c0d35136d75bc538372a317ca5df5a75ce73",
|
"url": "https://api.github.com/repos/web-auth/cose-lib/zipball/d72032843c97ba40edb682dfcec0b787b25cbe6f",
|
||||||
"reference": "2fe6c0d35136d75bc538372a317ca5df5a75ce73",
|
"reference": "d72032843c97ba40edb682dfcec0b787b25cbe6f",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -7338,7 +7480,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/web-auth/cose-lib/issues",
|
"issues": "https://github.com/web-auth/cose-lib/issues",
|
||||||
"source": "https://github.com/web-auth/cose-lib/tree/v4.0.5"
|
"source": "https://github.com/web-auth/cose-lib/tree/v4.0.6"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -7350,7 +7492,7 @@
|
||||||
"type": "patreon"
|
"type": "patreon"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-08-04T16:48:04+00:00"
|
"time": "2022-08-16T14:04:27+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "web-auth/metadata-service",
|
"name": "web-auth/metadata-service",
|
||||||
|
@ -8270,16 +8412,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "friendsofphp/php-cs-fixer",
|
"name": "friendsofphp/php-cs-fixer",
|
||||||
"version": "v3.9.5",
|
"version": "v3.10.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
|
"url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git",
|
||||||
"reference": "4465d70ba776806857a1ac2a6f877e582445ff36"
|
"reference": "76d7da666e66d83a1dc27a9d1c625c80cc4ac1fe"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/4465d70ba776806857a1ac2a6f877e582445ff36",
|
"url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/76d7da666e66d83a1dc27a9d1c625c80cc4ac1fe",
|
||||||
"reference": "4465d70ba776806857a1ac2a6f877e582445ff36",
|
"reference": "76d7da666e66d83a1dc27a9d1c625c80cc4ac1fe",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -8289,7 +8431,7 @@
|
||||||
"ext-json": "*",
|
"ext-json": "*",
|
||||||
"ext-tokenizer": "*",
|
"ext-tokenizer": "*",
|
||||||
"php": "^7.4 || ^8.0",
|
"php": "^7.4 || ^8.0",
|
||||||
"php-cs-fixer/diff": "^2.0",
|
"sebastian/diff": "^4.0",
|
||||||
"symfony/console": "^5.4 || ^6.0",
|
"symfony/console": "^5.4 || ^6.0",
|
||||||
"symfony/event-dispatcher": "^5.4 || ^6.0",
|
"symfony/event-dispatcher": "^5.4 || ^6.0",
|
||||||
"symfony/filesystem": "^5.4 || ^6.0",
|
"symfony/filesystem": "^5.4 || ^6.0",
|
||||||
|
@ -8347,7 +8489,7 @@
|
||||||
"description": "A tool to automatically fix PHP code style",
|
"description": "A tool to automatically fix PHP code style",
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues",
|
"issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues",
|
||||||
"source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.9.5"
|
"source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v3.10.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -8355,7 +8497,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-07-22T08:43:51+00:00"
|
"time": "2022-08-17T22:13:10+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hamcrest/hamcrest-php",
|
"name": "hamcrest/hamcrest-php",
|
||||||
|
@ -8738,304 +8880,25 @@
|
||||||
},
|
},
|
||||||
"time": "2022-02-21T01:04:05+00:00"
|
"time": "2022-02-21T01:04:05+00:00"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "php-cs-fixer/diff",
|
|
||||||
"version": "v2.0.2",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/PHP-CS-Fixer/diff.git",
|
|
||||||
"reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/PHP-CS-Fixer/diff/zipball/29dc0d507e838c4580d018bd8b5cb412474f7ec3",
|
|
||||||
"reference": "29dc0d507e838c4580d018bd8b5cb412474f7ec3",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": "^5.6 || ^7.0 || ^8.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpunit/phpunit": "^5.7.23 || ^6.4.3 || ^7.0",
|
|
||||||
"symfony/process": "^3.3"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"classmap": [
|
|
||||||
"src/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"BSD-3-Clause"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Sebastian Bergmann",
|
|
||||||
"email": "sebastian@phpunit.de"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Kore Nordmann",
|
|
||||||
"email": "mail@kore-nordmann.de"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "sebastian/diff v3 backport support for PHP 5.6+",
|
|
||||||
"homepage": "https://github.com/PHP-CS-Fixer",
|
|
||||||
"keywords": [
|
|
||||||
"diff"
|
|
||||||
],
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/PHP-CS-Fixer/diff/issues",
|
|
||||||
"source": "https://github.com/PHP-CS-Fixer/diff/tree/v2.0.2"
|
|
||||||
},
|
|
||||||
"time": "2020-10-14T08:32:19+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "phpdocumentor/reflection-common",
|
|
||||||
"version": "2.2.0",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/phpDocumentor/ReflectionCommon.git",
|
|
||||||
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/1d01c49d4ed62f25aa84a747ad35d5a16924662b",
|
|
||||||
"reference": "1d01c49d4ed62f25aa84a747ad35d5a16924662b",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": "^7.2 || ^8.0"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-2.x": "2.x-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"phpDocumentor\\Reflection\\": "src/"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Jaap van Otterdijk",
|
|
||||||
"email": "opensource@ijaap.nl"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Common reflection classes used by phpdocumentor to reflect the code structure",
|
|
||||||
"homepage": "http://www.phpdoc.org",
|
|
||||||
"keywords": [
|
|
||||||
"FQSEN",
|
|
||||||
"phpDocumentor",
|
|
||||||
"phpdoc",
|
|
||||||
"reflection",
|
|
||||||
"static analysis"
|
|
||||||
],
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/phpDocumentor/ReflectionCommon/issues",
|
|
||||||
"source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x"
|
|
||||||
},
|
|
||||||
"time": "2020-06-27T09:03:43+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "phpdocumentor/reflection-docblock",
|
|
||||||
"version": "5.3.0",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
|
|
||||||
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/622548b623e81ca6d78b721c5e029f4ce664f170",
|
|
||||||
"reference": "622548b623e81ca6d78b721c5e029f4ce664f170",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"ext-filter": "*",
|
|
||||||
"php": "^7.2 || ^8.0",
|
|
||||||
"phpdocumentor/reflection-common": "^2.2",
|
|
||||||
"phpdocumentor/type-resolver": "^1.3",
|
|
||||||
"webmozart/assert": "^1.9.1"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"mockery/mockery": "~1.3.2",
|
|
||||||
"psalm/phar": "^4.8"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "5.x-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"phpDocumentor\\Reflection\\": "src"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Mike van Riel",
|
|
||||||
"email": "me@mikevanriel.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Jaap van Otterdijk",
|
|
||||||
"email": "account@ijaap.nl"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues",
|
|
||||||
"source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0"
|
|
||||||
},
|
|
||||||
"time": "2021-10-19T17:43:47+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "phpdocumentor/type-resolver",
|
|
||||||
"version": "1.6.1",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/phpDocumentor/TypeResolver.git",
|
|
||||||
"reference": "77a32518733312af16a44300404e945338981de3"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/77a32518733312af16a44300404e945338981de3",
|
|
||||||
"reference": "77a32518733312af16a44300404e945338981de3",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": "^7.2 || ^8.0",
|
|
||||||
"phpdocumentor/reflection-common": "^2.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"ext-tokenizer": "*",
|
|
||||||
"psalm/phar": "^4.8"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-1.x": "1.x-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"phpDocumentor\\Reflection\\": "src"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Mike van Riel",
|
|
||||||
"email": "me@mikevanriel.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "A PSR-5 based resolver of Class names, Types and Structural Element Names",
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/phpDocumentor/TypeResolver/issues",
|
|
||||||
"source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1"
|
|
||||||
},
|
|
||||||
"time": "2022-03-15T21:29:03+00:00"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "phpspec/prophecy",
|
|
||||||
"version": "v1.15.0",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/phpspec/prophecy.git",
|
|
||||||
"reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
|
|
||||||
"reference": "bbcd7380b0ebf3961ee21409db7b38bc31d69a13",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"doctrine/instantiator": "^1.2",
|
|
||||||
"php": "^7.2 || ~8.0, <8.2",
|
|
||||||
"phpdocumentor/reflection-docblock": "^5.2",
|
|
||||||
"sebastian/comparator": "^3.0 || ^4.0",
|
|
||||||
"sebastian/recursion-context": "^3.0 || ^4.0"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"phpspec/phpspec": "^6.0 || ^7.0",
|
|
||||||
"phpunit/phpunit": "^8.0 || ^9.0"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"extra": {
|
|
||||||
"branch-alias": {
|
|
||||||
"dev-master": "1.x-dev"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"autoload": {
|
|
||||||
"psr-4": {
|
|
||||||
"Prophecy\\": "src/Prophecy"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Konstantin Kudryashov",
|
|
||||||
"email": "ever.zet@gmail.com",
|
|
||||||
"homepage": "http://everzet.com"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Marcello Duarte",
|
|
||||||
"email": "marcello.duarte@gmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Highly opinionated mocking framework for PHP 5.3+",
|
|
||||||
"homepage": "https://github.com/phpspec/prophecy",
|
|
||||||
"keywords": [
|
|
||||||
"Double",
|
|
||||||
"Dummy",
|
|
||||||
"fake",
|
|
||||||
"mock",
|
|
||||||
"spy",
|
|
||||||
"stub"
|
|
||||||
],
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/phpspec/prophecy/issues",
|
|
||||||
"source": "https://github.com/phpspec/prophecy/tree/v1.15.0"
|
|
||||||
},
|
|
||||||
"time": "2021-12-08T12:19:24+00:00"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-code-coverage",
|
"name": "phpunit/php-code-coverage",
|
||||||
"version": "9.2.15",
|
"version": "9.2.16",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
|
||||||
"reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f"
|
"reference": "2593003befdcc10db5e213f9f28814f5aa8ac073"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
|
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/2593003befdcc10db5e213f9f28814f5aa8ac073",
|
||||||
"reference": "2e9da11878c4202f97915c1cb4bb1ca318a63f5f",
|
"reference": "2593003befdcc10db5e213f9f28814f5aa8ac073",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-dom": "*",
|
"ext-dom": "*",
|
||||||
"ext-libxml": "*",
|
"ext-libxml": "*",
|
||||||
"ext-xmlwriter": "*",
|
"ext-xmlwriter": "*",
|
||||||
"nikic/php-parser": "^4.13.0",
|
"nikic/php-parser": "^4.14",
|
||||||
"php": ">=7.3",
|
"php": ">=7.3",
|
||||||
"phpunit/php-file-iterator": "^3.0.3",
|
"phpunit/php-file-iterator": "^3.0.3",
|
||||||
"phpunit/php-text-template": "^2.0.2",
|
"phpunit/php-text-template": "^2.0.2",
|
||||||
|
@ -9084,7 +8947,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
|
||||||
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.15"
|
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.16"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -9092,7 +8955,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-03-07T09:28:20+00:00"
|
"time": "2022-08-20T05:26:47+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/php-file-iterator",
|
"name": "phpunit/php-file-iterator",
|
||||||
|
@ -9337,16 +9200,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/phpunit",
|
"name": "phpunit/phpunit",
|
||||||
"version": "9.5.21",
|
"version": "9.5.23",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||||
"reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1"
|
"reference": "888556852e7e9bbeeedb9656afe46118765ade34"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0e32b76be457de00e83213528f6bb37e2a38fcb1",
|
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/888556852e7e9bbeeedb9656afe46118765ade34",
|
||||||
"reference": "0e32b76be457de00e83213528f6bb37e2a38fcb1",
|
"reference": "888556852e7e9bbeeedb9656afe46118765ade34",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -9361,7 +9224,6 @@
|
||||||
"phar-io/manifest": "^2.0.3",
|
"phar-io/manifest": "^2.0.3",
|
||||||
"phar-io/version": "^3.0.2",
|
"phar-io/version": "^3.0.2",
|
||||||
"php": ">=7.3",
|
"php": ">=7.3",
|
||||||
"phpspec/prophecy": "^1.12.1",
|
|
||||||
"phpunit/php-code-coverage": "^9.2.13",
|
"phpunit/php-code-coverage": "^9.2.13",
|
||||||
"phpunit/php-file-iterator": "^3.0.5",
|
"phpunit/php-file-iterator": "^3.0.5",
|
||||||
"phpunit/php-invoker": "^3.1.1",
|
"phpunit/php-invoker": "^3.1.1",
|
||||||
|
@ -9379,9 +9241,6 @@
|
||||||
"sebastian/type": "^3.0",
|
"sebastian/type": "^3.0",
|
||||||
"sebastian/version": "^3.0.2"
|
"sebastian/version": "^3.0.2"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
|
||||||
"phpspec/prophecy-phpunit": "^2.0.1"
|
|
||||||
},
|
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-soap": "*",
|
"ext-soap": "*",
|
||||||
"ext-xdebug": "*"
|
"ext-xdebug": "*"
|
||||||
|
@ -9423,7 +9282,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.21"
|
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.23"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -9435,7 +9294,7 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-06-19T12:14:25+00:00"
|
"time": "2022-08-22T14:01:36+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "pimple/pimple",
|
"name": "pimple/pimple",
|
||||||
|
@ -10886,16 +10745,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "spatie/ray",
|
"name": "spatie/ray",
|
||||||
"version": "1.35.0",
|
"version": "1.36.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/spatie/ray.git",
|
"url": "https://github.com/spatie/ray.git",
|
||||||
"reference": "7196737c17718264aef9e446b773ee490c1563dd"
|
"reference": "4a4def8cda4806218341b8204c98375aa8c34323"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/spatie/ray/zipball/7196737c17718264aef9e446b773ee490c1563dd",
|
"url": "https://api.github.com/repos/spatie/ray/zipball/4a4def8cda4806218341b8204c98375aa8c34323",
|
||||||
"reference": "7196737c17718264aef9e446b773ee490c1563dd",
|
"reference": "4a4def8cda4806218341b8204c98375aa8c34323",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -10945,7 +10804,7 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/spatie/ray/issues",
|
"issues": "https://github.com/spatie/ray/issues",
|
||||||
"source": "https://github.com/spatie/ray/tree/1.35.0"
|
"source": "https://github.com/spatie/ray/tree/1.36.0"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -10957,7 +10816,7 @@
|
||||||
"type": "other"
|
"type": "other"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2022-08-09T14:35:12+00:00"
|
"time": "2022-08-11T14:04:18+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/filesystem",
|
"name": "symfony/filesystem",
|
||||||
|
|
|
@ -46,7 +46,7 @@ return [
|
||||||
/*
|
/*
|
||||||
* Forbid user to reuse One Time Passwords.
|
* Forbid user to reuse One Time Passwords.
|
||||||
*/
|
*/
|
||||||
'forbid_old_passwords' => false,
|
'forbid_old_passwords' => true,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* User's table column for google2fa secret
|
* User's table column for google2fa secret
|
||||||
|
|
1515
package-lock.json
generated
1515
package-lock.json
generated
File diff suppressed because it is too large
Load diff
22
package.json
22
package.json
|
@ -13,31 +13,31 @@
|
||||||
"pre-commit": "lint-staged"
|
"pre-commit": "lint-staged"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@headlessui/vue": "^1.6.7",
|
||||||
|
"@kyvg/vue3-notification": "^2.3.4",
|
||||||
|
"@vueform/multiselect": "^2.3.2",
|
||||||
"autoprefixer": "^10.4.1",
|
"autoprefixer": "^10.4.1",
|
||||||
"axios": "^0.26.0",
|
"axios": "^0.27.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"dayjs": "^1.10.4",
|
"dayjs": "^1.10.4",
|
||||||
"laravel-mix": "^6.0.11",
|
"laravel-mix": "^6.0.11",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"portal-vue": "^2.1.7",
|
|
||||||
"postcss": "^8.4.5",
|
"postcss": "^8.4.5",
|
||||||
"postcss-import": "^14.0.0",
|
"postcss-import": "^14.0.0",
|
||||||
"resolve-url-loader": "^5.0.0",
|
"resolve-url-loader": "^5.0.0",
|
||||||
"tailwindcss": "^3.0.11",
|
"tailwindcss": "^3.0.11",
|
||||||
"tippy.js": "^6.2.7",
|
"tippy.js": "^6.2.7",
|
||||||
"v-clipboard": "^2.2.3",
|
"v-clipboard": "github:euvl/v-clipboard",
|
||||||
"vue": "^2.6.12",
|
"vue": "^3.0.0",
|
||||||
"vue-good-table": "^2.21.3",
|
"vue-good-table-next": "^0.2.1",
|
||||||
"vue-loader": "^15.9.6",
|
"vue-loader": "^17.0.0",
|
||||||
"vue-multiselect": "^2.1.6",
|
|
||||||
"vue-notification": "^1.3.20",
|
|
||||||
"vue-template-compiler": "^2.6.12",
|
"vue-template-compiler": "^2.6.12",
|
||||||
"vuedraggable": "^2.24.2"
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"css-loader": "^6.0.0",
|
"css-loader": "^6.0.0",
|
||||||
"husky": "^7.0.0",
|
"husky": "^8.0.0",
|
||||||
"lint-staged": "^12.0.0",
|
"lint-staged": "^13.0.0",
|
||||||
"prettier": "^2.2.1"
|
"prettier": "^2.2.1"
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
<env name="APP_ENV" value="testing"/>
|
<env name="APP_ENV" value="testing"/>
|
||||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||||
<env name="CACHE_DRIVER" value="array"/>
|
<env name="CACHE_DRIVER" value="array"/>
|
||||||
<env name="MAIL_DRIVER" value="array"/>
|
<env name="MAIL_MAILER" value="array"/>
|
||||||
<env name="QUEUE_CONNECTION" value="sync"/>
|
<env name="QUEUE_CONNECTION" value="sync"/>
|
||||||
<env name="SESSION_DRIVER" value="array"/>
|
<env name="SESSION_DRIVER" value="array"/>
|
||||||
<env name="DB_CONNECTION" value="sqlite"/>
|
<env name="DB_CONNECTION" value="sqlite"/>
|
||||||
|
|
25
resources/css/app.css
vendored
25
resources/css/app.css
vendored
|
@ -31,20 +31,27 @@ html {
|
||||||
@apply bg-yellow-100 border-yellow-600 text-yellow-800;
|
@apply bg-yellow-100 border-yellow-600 text-yellow-800;
|
||||||
}
|
}
|
||||||
|
|
||||||
.multiselect .multiselect__tag {
|
.multiselect .multiselect-dropdown::-webkit-scrollbar {
|
||||||
|
@apply w-2 h-2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect .multiselect-dropdown::-webkit-scrollbar-thumb {
|
||||||
|
@apply bg-indigo-500 rounded-full;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect .multiselect-dropdown::-webkit-scrollbar-track {
|
||||||
|
@apply bg-indigo-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
.multiselect .multiselect-tag {
|
||||||
@apply bg-indigo-100 text-indigo-900;
|
@apply bg-indigo-100 text-indigo-900;
|
||||||
}
|
}
|
||||||
|
|
||||||
.multiselect .multiselect__tag-icon:focus,
|
.multiselect .multiselect-option.is-selected {
|
||||||
.multiselect .multiselect__tag-icon:hover {
|
@apply bg-indigo-100 text-indigo-900;
|
||||||
@apply bg-indigo-400;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.multiselect .multiselect__option--highlight {
|
.multiselect .multiselect-option.is-selected:hover {
|
||||||
@apply bg-indigo-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.multiselect .multiselect__option--selected.multiselect__option--highlight {
|
|
||||||
@apply bg-red-500;
|
@apply bg-red-500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
95
resources/js/app.js
vendored
95
resources/js/app.js
vendored
|
@ -9,59 +9,58 @@ dayjs.extend(advancedFormat)
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
import Vue from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
|
||||||
import PortalVue from 'portal-vue'
|
import Clipboard from 'v-clipboard/src'
|
||||||
import Clipboard from 'v-clipboard'
|
import Notifications from '@kyvg/vue3-notification'
|
||||||
import Notifications from 'vue-notification'
|
import VueGoodTablePlugin from 'vue-good-table-next'
|
||||||
import VueGoodTablePlugin from 'vue-good-table'
|
|
||||||
|
|
||||||
Vue.use(PortalVue)
|
const app = createApp({
|
||||||
Vue.use(Clipboard)
|
|
||||||
Vue.use(Notifications)
|
|
||||||
Vue.use(VueGoodTablePlugin)
|
|
||||||
|
|
||||||
Vue.component('loader', require('./components/Loader.vue').default)
|
|
||||||
Vue.component('dropdown', require('./components/DropdownNav.vue').default)
|
|
||||||
Vue.component('icon', require('./components/Icon.vue').default)
|
|
||||||
|
|
||||||
Vue.component('aliases', require('./pages/Aliases.vue').default)
|
|
||||||
Vue.component('recipients', require('./pages/Recipients.vue').default)
|
|
||||||
Vue.component('domains', require('./pages/Domains.vue').default)
|
|
||||||
Vue.component('usernames', require('./pages/Usernames.vue').default)
|
|
||||||
Vue.component('rules', require('./pages/Rules.vue').default)
|
|
||||||
Vue.component('failed-deliveries', require('./pages/FailedDeliveries.vue').default)
|
|
||||||
|
|
||||||
Vue.component(
|
|
||||||
'personal-access-tokens',
|
|
||||||
require('./components/sanctum/PersonalAccessTokens.vue').default
|
|
||||||
)
|
|
||||||
Vue.component('webauthn-keys', require('./components/WebauthnKeys.vue').default)
|
|
||||||
|
|
||||||
Vue.filter('formatDate', value => {
|
|
||||||
return dayjs.utc(value).local().format('Do MMM YYYY')
|
|
||||||
})
|
|
||||||
|
|
||||||
Vue.filter('formatDateTime', value => {
|
|
||||||
return dayjs.utc(value).local().format('Do MMM YYYY h:mm A')
|
|
||||||
})
|
|
||||||
|
|
||||||
Vue.filter('timeAgo', value => {
|
|
||||||
return dayjs.utc(value).fromNow()
|
|
||||||
})
|
|
||||||
|
|
||||||
Vue.filter('truncate', (string, value) => {
|
|
||||||
if (value >= string.length) {
|
|
||||||
return string
|
|
||||||
}
|
|
||||||
return string.substring(0, value) + '...'
|
|
||||||
})
|
|
||||||
|
|
||||||
const app = new Vue({
|
|
||||||
el: '#app',
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
mobileNavActive: false,
|
mobileNavActive: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
app.use(Clipboard)
|
||||||
|
app.use(Notifications)
|
||||||
|
app.use(VueGoodTablePlugin)
|
||||||
|
|
||||||
|
app.component('loader', require('./components/Loader.vue').default)
|
||||||
|
app.component('dropdown', require('./components/DropdownNav.vue').default)
|
||||||
|
app.component('icon', require('./components/Icon.vue').default)
|
||||||
|
|
||||||
|
app.component('aliases', require('./pages/Aliases.vue').default)
|
||||||
|
app.component('recipients', require('./pages/Recipients.vue').default)
|
||||||
|
app.component('domains', require('./pages/Domains.vue').default)
|
||||||
|
app.component('usernames', require('./pages/Usernames.vue').default)
|
||||||
|
app.component('rules', require('./pages/Rules.vue').default)
|
||||||
|
app.component('failed-deliveries', require('./pages/FailedDeliveries.vue').default)
|
||||||
|
|
||||||
|
app.component(
|
||||||
|
'personal-access-tokens',
|
||||||
|
require('./components/sanctum/PersonalAccessTokens.vue').default
|
||||||
|
)
|
||||||
|
app.component('webauthn-keys', require('./components/WebauthnKeys.vue').default)
|
||||||
|
|
||||||
|
// Global filters
|
||||||
|
app.config.globalProperties.$filters = {
|
||||||
|
formatDate(value) {
|
||||||
|
return dayjs.utc(value).local().format('Do MMM YYYY')
|
||||||
|
},
|
||||||
|
formatDateTime(value) {
|
||||||
|
return dayjs.utc(value).local().format('Do MMM YYYY h:mm A')
|
||||||
|
},
|
||||||
|
timeAgo(value) {
|
||||||
|
return dayjs.utc(value).fromNow()
|
||||||
|
},
|
||||||
|
truncate(value, length) {
|
||||||
|
if (length >= value.length) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return value.substring(0, length) + '...'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
|
|
|
@ -1,148 +1,66 @@
|
||||||
|
<!-- This example requires Tailwind CSS v2.0+ -->
|
||||||
<template>
|
<template>
|
||||||
<portal to="modals">
|
<TransitionRoot as="template" :show="open">
|
||||||
<div
|
<Dialog as="div" class="relative z-10" @close="open = false">
|
||||||
v-if="showModal"
|
<TransitionChild
|
||||||
class="fixed inset-0 flex justify-center"
|
as="template"
|
||||||
:class="overflow ? 'overflow-auto' : 'items-center'"
|
enter="ease-out duration-300"
|
||||||
|
enter-from="opacity-0"
|
||||||
|
enter-to="opacity-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leave-from="opacity-100"
|
||||||
|
leave-to="opacity-0"
|
||||||
>
|
>
|
||||||
<transition
|
<div class="fixed inset-0 bg-grey-500 bg-opacity-75 transition-opacity" />
|
||||||
@before-leave="backdropLeaving = true"
|
</TransitionChild>
|
||||||
@after-leave="backdropLeaving = false"
|
|
||||||
enter-active-class="transition-all transition-fast ease-out-quad"
|
|
||||||
leave-active-class="transition-all transition-medium ease-in-quad"
|
|
||||||
enter-class="opacity-0"
|
|
||||||
enter-to-class="opacity-100"
|
|
||||||
leave-class="opacity-100"
|
|
||||||
leave-to-class="opacity-0"
|
|
||||||
appear
|
|
||||||
>
|
|
||||||
<div v-if="showBackdrop">
|
|
||||||
<div
|
|
||||||
class="inset-0 bg-black opacity-25"
|
|
||||||
:class="overflow ? 'fixed pointer-events-none' : 'absolute'"
|
|
||||||
@click="close"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
|
|
||||||
<transition
|
<div class="fixed z-10 inset-0 overflow-y-auto">
|
||||||
@before-leave="cardLeaving = true"
|
<div
|
||||||
@after-leave="cardLeaving = false"
|
class="flex items-end sm:items-center justify-center min-h-full p-4 text-center sm:p-0"
|
||||||
enter-active-class="transition-all transition-fast ease-out-quad"
|
|
||||||
leave-active-class="transition-all transition-medium ease-in-quad"
|
|
||||||
enter-class="opacity-0 scale-70"
|
|
||||||
enter-to-class="opacity-100 scale-100"
|
|
||||||
leave-class="opacity-100 scale-100"
|
|
||||||
leave-to-class="opacity-0 scale-70"
|
|
||||||
appear
|
|
||||||
>
|
>
|
||||||
<div v-if="showContent" class="relative">
|
<TransitionChild
|
||||||
<slot></slot>
|
as="template"
|
||||||
|
enter="ease-out duration-300"
|
||||||
|
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
enter-to="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leave="ease-in duration-200"
|
||||||
|
leave-from="opacity-100 translate-y-0 sm:scale-100"
|
||||||
|
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||||
|
>
|
||||||
|
<DialogPanel
|
||||||
|
class="relative bg-white rounded-lg px-4 pt-5 pb-4 text-left shadow-xl transform transition-all sm:my-8 sm:w-full sm:p-6"
|
||||||
|
:class="maxWidth ? maxWidth : 'sm:max-w-lg'"
|
||||||
|
>
|
||||||
|
<div class="mt-3 text-center sm:mt-0 sm:text-left">
|
||||||
|
<DialogTitle
|
||||||
|
as="h2"
|
||||||
|
class="text-2xl leading-tight font-medium text-grey-900 border-b-2 border-grey-100 pb-4"
|
||||||
|
>
|
||||||
|
<slot name="title"></slot>
|
||||||
|
</DialogTitle>
|
||||||
|
<div class="mt-2">
|
||||||
|
<slot name="content"></slot>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
|
||||||
</div>
|
</div>
|
||||||
</portal>
|
</DialogPanel>
|
||||||
|
</TransitionChild>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</TransitionRoot>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { Dialog, DialogPanel, DialogTitle, TransitionChild, TransitionRoot } from '@headlessui/vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: ['open', 'maxWidth'],
|
||||||
open: {
|
components: {
|
||||||
type: Boolean,
|
Dialog,
|
||||||
required: true,
|
DialogPanel,
|
||||||
},
|
DialogTitle,
|
||||||
overflow: {
|
TransitionChild,
|
||||||
type: Boolean,
|
TransitionRoot,
|
||||||
required: false,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
showModal: false,
|
|
||||||
showBackdrop: false,
|
|
||||||
showContent: false,
|
|
||||||
backdropLeaving: false,
|
|
||||||
cardLeaving: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
const onEscape = e => {
|
|
||||||
if (this.open && e.keyCode === 27) {
|
|
||||||
this.close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.addEventListener('keydown', onEscape)
|
|
||||||
this.$once('hook:destroyed', () => {
|
|
||||||
document.removeEventListener('keydown', onEscape)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
open: {
|
|
||||||
handler: function (newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
this.show()
|
|
||||||
} else {
|
|
||||||
this.close()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
immediate: true,
|
|
||||||
},
|
|
||||||
leaving(newValue) {
|
|
||||||
if (newValue === false) {
|
|
||||||
this.showModal = false
|
|
||||||
this.$emit('close')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
leaving() {
|
|
||||||
return this.backdropLeaving || this.cardLeaving
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
show() {
|
|
||||||
this.showModal = true
|
|
||||||
this.showBackdrop = true
|
|
||||||
this.showContent = true
|
|
||||||
},
|
|
||||||
close() {
|
|
||||||
this.showBackdrop = false
|
|
||||||
this.showContent = false
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
|
||||||
.origin-top-right {
|
|
||||||
transform-origin: top right;
|
|
||||||
}
|
|
||||||
.transition-all {
|
|
||||||
transition-property: all;
|
|
||||||
}
|
|
||||||
.transition-fastest {
|
|
||||||
transition-duration: 50ms;
|
|
||||||
}
|
|
||||||
.transition-faster {
|
|
||||||
transition-duration: 100ms;
|
|
||||||
}
|
|
||||||
.transition-fast {
|
|
||||||
transition-duration: 150ms;
|
|
||||||
}
|
|
||||||
.transition-medium {
|
|
||||||
transition-duration: 200ms;
|
|
||||||
}
|
|
||||||
.ease-out-quad {
|
|
||||||
transition-timing-function: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
||||||
}
|
|
||||||
.ease-in-quad {
|
|
||||||
transition-timing-function: cubic-bezier(0.55, 0.085, 0.68, 0.53);
|
|
||||||
}
|
|
||||||
.scale-70 {
|
|
||||||
transform: scale(0.7);
|
|
||||||
}
|
|
||||||
.scale-100 {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -1,65 +1,35 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="relative flex justify-end items-center" @keydown.escape="isOpen = false">
|
<Menu as="div" class="relative inline-block text-left">
|
||||||
<button
|
<div>
|
||||||
ref="openOptions"
|
<MenuButton class="flex items-center text-grey-400 hover:text-grey-600 focus:outline-none">
|
||||||
@click="isOpen = !isOpen"
|
|
||||||
:aria-expanded="isOpen"
|
|
||||||
id="project-options-menu-0"
|
|
||||||
aria-has-popup="true"
|
|
||||||
type="button"
|
|
||||||
class="w-8 h-8 bg-white inline-flex items-center justify-center text-grey-400 rounded-full hover:text-grey-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500"
|
|
||||||
>
|
|
||||||
<span class="sr-only">Open options</span>
|
<span class="sr-only">Open options</span>
|
||||||
|
|
||||||
<icon
|
<icon
|
||||||
name="more"
|
name="more"
|
||||||
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer outline-none"
|
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer outline-none"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
/>
|
/>
|
||||||
</button>
|
</MenuButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
<transition
|
<transition
|
||||||
enter-active-class="transition ease-out duration-100"
|
enter-active-class="transition ease-out duration-100"
|
||||||
enter-class="opacity-0 scale-95"
|
enter-from-class="transform opacity-0 scale-95"
|
||||||
enter-to-class="opacity-100 scale-100"
|
enter-to-class="transform opacity-100 scale-100"
|
||||||
leave-active-class="transition ease-in duration-75"
|
leave-active-class="transition ease-in duration-75"
|
||||||
leave-class="opacity-100 scale-100"
|
leave-from-class="transform opacity-100 scale-100"
|
||||||
leave-to-class="opacity-0 scale-95"
|
leave-to-class="transform opacity-0 scale-95"
|
||||||
>
|
>
|
||||||
<div
|
<MenuItems
|
||||||
v-show="isOpen"
|
class="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-10"
|
||||||
class="mx-3 origin-top-right absolute right-7 top-0 w-48 mt-1 rounded-md shadow-lg z-10 bg-white ring-1 ring-black ring-opacity-5 divide-y divide-grey-200"
|
|
||||||
role="menu"
|
|
||||||
aria-orientation="vertical"
|
|
||||||
aria-labelledby="project-options-menu-0"
|
|
||||||
>
|
>
|
||||||
|
<div class="py-1">
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
|
</MenuItems>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</Menu>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
export default {
|
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isOpen: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
window.addEventListener('click', this.close)
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
window.removeEventListener('click', this.close)
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
close(e) {
|
|
||||||
if (!this.$refs.openOptions.contains(e.target)) {
|
|
||||||
this.isOpen = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
<template>
|
<template>
|
||||||
<button
|
<Switch
|
||||||
|
v-model="modelValue"
|
||||||
@click="toggle"
|
@click="toggle"
|
||||||
type="button"
|
:class="[
|
||||||
:aria-pressed="value.toString()"
|
modelValue ? 'bg-cyan-500' : 'bg-grey-300',
|
||||||
:class="this.value ? 'bg-cyan-500' : 'bg-grey-300'"
|
'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none',
|
||||||
class="relative inline-flex shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none"
|
]"
|
||||||
>
|
>
|
||||||
<span class="sr-only">Use setting</span>
|
<span class="sr-only">Use setting</span>
|
||||||
<span
|
<span
|
||||||
:class="this.value ? 'translate-x-5' : 'translate-x-0'"
|
:class="[
|
||||||
class="relative inline-block h-5 w-5 rounded-full bg-white shadow ring-0 transition ease-in-out duration-200"
|
modelValue ? 'translate-x-5' : 'translate-x-0',
|
||||||
|
'pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200',
|
||||||
|
]"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
:class="this.value ? 'opacity-0 ease-out duration-100' : 'opacity-100 ease-in duration-200'"
|
:class="[
|
||||||
class="absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
|
modelValue ? 'opacity-0 ease-out duration-100' : 'opacity-100 ease-in duration-200',
|
||||||
|
'absolute inset-0 h-full w-full flex items-center justify-center transition-opacity',
|
||||||
|
]"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<svg class="h-3 w-3 text-grey-400" fill="none" viewBox="0 0 12 12">
|
<svg class="h-3 w-3 text-grey-400" fill="none" viewBox="0 0 12 12">
|
||||||
|
@ -27,8 +32,10 @@
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
:class="this.value ? 'opacity-100 ease-in duration-200' : 'opacity-0 ease-out duration-100'"
|
:class="[
|
||||||
class="absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
|
modelValue ? 'opacity-100 ease-in duration-200' : 'opacity-0 ease-out duration-100',
|
||||||
|
'absolute inset-0 h-full w-full flex items-center justify-center transition-opacity',
|
||||||
|
]"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
<svg class="h-3 w-3 text-cyan-500" fill="currentColor" viewBox="0 0 12 12">
|
<svg class="h-3 w-3 text-cyan-500" fill="currentColor" viewBox="0 0 12 12">
|
||||||
|
@ -38,16 +45,21 @@
|
||||||
</svg>
|
</svg>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</Switch>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { Switch } from '@headlessui/vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['value'],
|
props: ['modelValue'],
|
||||||
|
components: {
|
||||||
|
Switch,
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggle() {
|
toggle() {
|
||||||
this.$emit('input', !this.value)
|
this.$emit('update:modelValue', !this.modelValue)
|
||||||
this.value ? this.$emit('off') : this.$emit('on')
|
this.modelValue ? this.$emit('off') : this.$emit('on')
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div v-for="key in keys" :key="key.id" class="table-row even:bg-grey-50 odd:bg-white">
|
<div v-for="key in keys" :key="key.id" class="table-row even:bg-grey-50 odd:bg-white">
|
||||||
<div class="table-cell p-1 md:p-4">{{ key.name }}</div>
|
<div class="table-cell p-1 md:p-4">{{ key.name }}</div>
|
||||||
<div class="table-cell p-1 md:p-4">{{ key.created_at | timeAgo }}</div>
|
<div class="table-cell p-1 md:p-4">{{ $filters.timeAgo(key.created_at) }}</div>
|
||||||
<div class="table-cell p-1 md:p-4">
|
<div class="table-cell p-1 md:p-4">
|
||||||
<Toggle v-model="key.enabled" @on="enableKey(key.id)" @off="disableKey(key.id)" />
|
<Toggle v-model="key.enabled" @on="enableKey(key.id)" @off="disableKey(key.id)" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,12 +43,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal :open="deleteKeyModalOpen" @close="closeDeleteKeyModal">
|
<Modal :open="deleteKeyModalOpen" @close="closeDeleteKeyModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
|
<template v-slot:title> Remove Hardware Key </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Remove Hardware Key
|
|
||||||
</h2>
|
|
||||||
<p v-if="keys.length === 1" class="my-4 text-grey-700">
|
<p v-if="keys.length === 1" class="my-4 text-grey-700">
|
||||||
Once this key is removed, <b>Two-Factor Authentication</b> will be disabled on your
|
Once this key is removed, <b>Two-Factor Authentication</b> will be disabled on your
|
||||||
account.
|
account.
|
||||||
|
@ -74,7 +70,7 @@
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -77,13 +77,13 @@
|
||||||
class="table-row even:bg-grey-50 odd:bg-white"
|
class="table-row even:bg-grey-50 odd:bg-white"
|
||||||
>
|
>
|
||||||
<div class="table-cell p-1 md:p-4">{{ token.name }}</div>
|
<div class="table-cell p-1 md:p-4">{{ token.name }}</div>
|
||||||
<div class="table-cell p-1 md:p-4">{{ token.created_at | timeAgo }}</div>
|
<div class="table-cell p-1 md:p-4">{{ $filters.timeAgo(token.created_at) }}</div>
|
||||||
<div v-if="token.last_used_at" class="table-cell p-1 md:p-4">
|
<div v-if="token.last_used_at" class="table-cell p-1 md:p-4">
|
||||||
{{ token.last_used_at | timeAgo }}
|
{{ $filters.timeAgo(token.last_used_at) }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="table-cell p-1 md:p-4">Not used yet</div>
|
<div v-else class="table-cell p-1 md:p-4">Not used yet</div>
|
||||||
<div v-if="token.expires_at" class="table-cell p-1 md:p-4">
|
<div v-if="token.expires_at" class="table-cell p-1 md:p-4">
|
||||||
{{ token.expires_at | formatDate }}
|
{{ $filters.formatDate(token.expires_at) }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="table-cell p-1 md:p-4">Does not expire</div>
|
<div v-else class="table-cell p-1 md:p-4">Does not expire</div>
|
||||||
<div class="table-cell p-1 md:p-4 text-right">
|
<div class="table-cell p-1 md:p-4 text-right">
|
||||||
|
@ -100,12 +100,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal :open="createTokenModalOpen" @close="closeCreateTokenModal">
|
<Modal :open="createTokenModalOpen" @close="closeCreateTokenModal">
|
||||||
<div v-if="!accessToken" class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
|
<template v-if="!accessToken" v-slot:title> Create New Token </template>
|
||||||
<h2
|
<template v-else v-slot:title> Personal Access Token </template>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
<template v-slot:content>
|
||||||
>
|
<div v-show="!accessToken">
|
||||||
Create New Token
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
What's this token going to be used for? Give it a short name so that you remember later.
|
What's this token going to be used for? Give it a short name so that you remember later.
|
||||||
You can also select an expiry date for the token if you wish.
|
You can also select an expiry date for the token if you wish.
|
||||||
|
@ -174,12 +172,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
|
<div v-show="accessToken">
|
||||||
<h2
|
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Personal Access Token
|
|
||||||
</h2>
|
|
||||||
<p class="my-4 text-grey-700">
|
<p class="my-4 text-grey-700">
|
||||||
This is your new personal access token. This is the only time the token will ever be
|
This is your new personal access token. This is the only time the token will ever be
|
||||||
displayed, so please make a note of it in a safe place (e.g. password manager)!
|
displayed, so please make a note of it in a safe place (e.g. password manager)!
|
||||||
|
@ -193,6 +186,13 @@
|
||||||
readonly
|
readonly
|
||||||
>
|
>
|
||||||
</textarea>
|
</textarea>
|
||||||
|
<div class="text-center">
|
||||||
|
<img :src="qrCode" class="inline-block" alt="QR Code" />
|
||||||
|
<p class="text-left text-sm text-grey-700">
|
||||||
|
You can scan this QR code to automatically login to the AnonAddy for Android mobile
|
||||||
|
app.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<button
|
<button
|
||||||
class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
|
class="bg-cyan-400 hover:bg-cyan-300 text-cyan-900 font-bold py-3 px-4 rounded focus:outline-none"
|
||||||
|
@ -210,15 +210,12 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="revokeTokenModalOpen" @close="closeRevokeTokenModal">
|
<Modal :open="revokeTokenModalOpen" @close="closeRevokeTokenModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
|
<template v-slot:title> ARevoke API Access Token </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Revoke API Access Token
|
|
||||||
</h2>
|
|
||||||
<p class="my-4 text-grey-700">
|
<p class="my-4 text-grey-700">
|
||||||
Any browser extension, application or script using this API access token will no longer be
|
Any browser extension, application or script using this API access token will no longer be
|
||||||
able to access the API. This action cannot be undone.
|
able to access the API. This action cannot be undone.
|
||||||
|
@ -240,7 +237,7 @@
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -255,6 +252,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
accessToken: null,
|
accessToken: null,
|
||||||
|
qrCode: null,
|
||||||
createTokenModalOpen: false,
|
createTokenModalOpen: false,
|
||||||
revokeTokenModalOpen: false,
|
revokeTokenModalOpen: false,
|
||||||
tokens: [],
|
tokens: [],
|
||||||
|
@ -285,6 +283,7 @@ export default {
|
||||||
store() {
|
store() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.accessToken = null
|
this.accessToken = null
|
||||||
|
this.qrCode = null
|
||||||
this.form.errors = {}
|
this.form.errors = {}
|
||||||
|
|
||||||
axios
|
axios
|
||||||
|
@ -297,6 +296,7 @@ export default {
|
||||||
|
|
||||||
this.tokens.push(response.data.token)
|
this.tokens.push(response.data.token)
|
||||||
this.accessToken = response.data.accessToken
|
this.accessToken = response.data.accessToken
|
||||||
|
this.qrCode = response.data.qrCode
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
|
@ -330,6 +330,7 @@ export default {
|
||||||
},
|
},
|
||||||
openCreateTokenModal() {
|
openCreateTokenModal() {
|
||||||
this.accessToken = null
|
this.accessToken = null
|
||||||
|
this.qrCode = null
|
||||||
this.createTokenModalOpen = true
|
this.createTokenModalOpen = true
|
||||||
},
|
},
|
||||||
closeCreateTokenModal() {
|
closeCreateTokenModal() {
|
||||||
|
|
|
@ -98,7 +98,7 @@
|
||||||
/>
|
/>
|
||||||
<icon
|
<icon
|
||||||
v-if="search"
|
v-if="search"
|
||||||
@click.native="search = ''"
|
@click="search = ''"
|
||||||
name="close-circle"
|
name="close-circle"
|
||||||
class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
|
class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
|
||||||
/>
|
/>
|
||||||
|
@ -146,9 +146,9 @@
|
||||||
|
|
||||||
<vue-good-table
|
<vue-good-table
|
||||||
v-if="initialAliases.length"
|
v-if="initialAliases.length"
|
||||||
@on-search="debounceToolips"
|
v-on:search="debounceToolips"
|
||||||
@on-page-change="debounceToolips"
|
v-on:page-change="debounceToolips"
|
||||||
@on-per-page-change="debounceToolips"
|
v-on:per-page-change="debounceToolips"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:rows="rows"
|
:rows="rows"
|
||||||
:search-options="{
|
:search-options="{
|
||||||
|
@ -169,10 +169,10 @@
|
||||||
}"
|
}"
|
||||||
styleClass="vgt-table"
|
styleClass="vgt-table"
|
||||||
>
|
>
|
||||||
<div slot="emptystate" class="flex items-center justify-center h-24 text-lg text-grey-700">
|
<template #emptystate class="flex items-center justify-center h-24 text-lg text-grey-700">
|
||||||
No aliases found for that search!
|
No aliases found for that search!
|
||||||
</div>
|
</template>
|
||||||
<template slot="table-row" slot-scope="props">
|
<template #table-row="props">
|
||||||
<span v-if="props.column.field == 'created_at'" class="flex items-center">
|
<span v-if="props.column.field == 'created_at'" class="flex items-center">
|
||||||
<span
|
<span
|
||||||
:class="`bg-${getAliasStatus(props.row).colour}-100`"
|
:class="`bg-${getAliasStatus(props.row).colour}-100`"
|
||||||
|
@ -187,8 +187,8 @@
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
class="tooltip outline-none text-sm whitespace-nowrap"
|
class="tooltip outline-none text-sm whitespace-nowrap"
|
||||||
:data-tippy-content="rows[props.row.originalIndex].created_at | formatDate"
|
:data-tippy-content="$filters.formatDate(rows[props.row.originalIndex].created_at)"
|
||||||
>{{ props.row.created_at | timeAgo }}
|
>{{ $filters.timeAgo(props.row.created_at) }}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="props.column.field == 'email'" class="block">
|
<span v-else-if="props.column.field == 'email'" class="block">
|
||||||
|
@ -199,10 +199,10 @@
|
||||||
v-clipboard:success="clipboardSuccess"
|
v-clipboard:success="clipboardSuccess"
|
||||||
v-clipboard:error="clipboardError"
|
v-clipboard:error="clipboardError"
|
||||||
><span class="font-semibold text-indigo-800">{{
|
><span class="font-semibold text-indigo-800">{{
|
||||||
getAliasLocalPart(props.row) | truncate(60)
|
$filters.truncate(getAliasLocalPart(props.row), 60)
|
||||||
}}</span
|
}}</span
|
||||||
><span v-if="getAliasLocalPart(props.row).length <= 60">{{
|
><span v-if="getAliasLocalPart(props.row).length <= 60">{{
|
||||||
('@' + props.row.domain) | truncate(60 - getAliasLocalPart(props.row).length)
|
$filters.truncate('@' + props.row.domain, 60 - getAliasLocalPart(props.row).length)
|
||||||
}}</span>
|
}}</span>
|
||||||
</span>
|
</span>
|
||||||
<div v-if="aliasIdToEdit === props.row.id" class="flex items-center">
|
<div v-if="aliasIdToEdit === props.row.id" class="flex items-center">
|
||||||
|
@ -220,22 +220,22 @@
|
||||||
<icon
|
<icon
|
||||||
name="close"
|
name="close"
|
||||||
class="inline-block w-6 h-6 text-red-300 fill-current cursor-pointer"
|
class="inline-block w-6 h-6 text-red-300 fill-current cursor-pointer"
|
||||||
@click.native="aliasIdToEdit = aliasDescriptionToEdit = ''"
|
@click="aliasIdToEdit = aliasDescriptionToEdit = ''"
|
||||||
/>
|
/>
|
||||||
<icon
|
<icon
|
||||||
name="save"
|
name="save"
|
||||||
class="inline-block w-6 h-6 text-cyan-500 fill-current cursor-pointer"
|
class="inline-block w-6 h-6 text-cyan-500 fill-current cursor-pointer"
|
||||||
@click.native="editAlias(rows[props.row.originalIndex])"
|
@click="editAlias(rows[props.row.originalIndex])"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="props.row.description" class="flex items-center">
|
<div v-else-if="props.row.description" class="flex items-center">
|
||||||
<span class="inline-block text-grey-400 text-sm py-1 border border-transparent">
|
<span class="inline-block text-grey-400 text-sm py-1 border border-transparent">
|
||||||
{{ props.row.description | truncate(60) }}
|
{{ $filters.truncate(props.row.description, 60) }}
|
||||||
</span>
|
</span>
|
||||||
<icon
|
<icon
|
||||||
name="edit"
|
name="edit"
|
||||||
class="inline-block w-6 h-6 ml-2 text-grey-300 fill-current cursor-pointer"
|
class="inline-block w-6 h-6 ml-2 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="
|
@click="
|
||||||
;(aliasIdToEdit = props.row.id), (aliasDescriptionToEdit = props.row.description)
|
;(aliasIdToEdit = props.row.id), (aliasDescriptionToEdit = props.row.description)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
@ -279,7 +279,7 @@
|
||||||
<icon
|
<icon
|
||||||
name="edit"
|
name="edit"
|
||||||
class="ml-2 inline-block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="ml-2 inline-block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="openAliasRecipientsModal(props.row)"
|
@click="openAliasRecipientsModal(props.row)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
@ -311,6 +311,7 @@
|
||||||
<span v-else class="flex items-center justify-center outline-none" tabindex="-1">
|
<span v-else class="flex items-center justify-center outline-none" tabindex="-1">
|
||||||
<more-options>
|
<more-options>
|
||||||
<div role="none">
|
<div role="none">
|
||||||
|
<MenuItem>
|
||||||
<span
|
<span
|
||||||
@click="openSendFromModal(props.row)"
|
@click="openSendFromModal(props.row)"
|
||||||
class="group cursor-pointer flex items-center px-4 py-3 text-sm text-grey-700 hover:bg-grey-100 hover:text-grey-900"
|
class="group cursor-pointer flex items-center px-4 py-3 text-sm text-grey-700 hover:bg-grey-100 hover:text-grey-900"
|
||||||
|
@ -319,8 +320,9 @@
|
||||||
<icon name="send" class="block mr-3 w-5 h-5 text-grey-300 outline-none" />
|
<icon name="send" class="block mr-3 w-5 h-5 text-grey-300 outline-none" />
|
||||||
Send From
|
Send From
|
||||||
</span>
|
</span>
|
||||||
|
</MenuItem>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="props.row.deleted_at" role="none">
|
<MenuItem v-if="props.row.deleted_at">
|
||||||
<span
|
<span
|
||||||
@click="openRestoreModal(props.row.id)"
|
@click="openRestoreModal(props.row.id)"
|
||||||
class="group cursor-pointer flex items-center px-4 py-3 text-sm text-grey-700 hover:bg-grey-100 hover:text-grey-900"
|
class="group cursor-pointer flex items-center px-4 py-3 text-sm text-grey-700 hover:bg-grey-100 hover:text-grey-900"
|
||||||
|
@ -332,8 +334,8 @@
|
||||||
/>
|
/>
|
||||||
Restore
|
Restore
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</MenuItem>
|
||||||
<div v-else role="none">
|
<MenuItem v-else>
|
||||||
<span
|
<span
|
||||||
@click="openDeleteModal(props.row)"
|
@click="openDeleteModal(props.row)"
|
||||||
class="group cursor-pointer flex items-center px-4 py-3 text-sm text-grey-700 hover:bg-grey-100 hover:text-grey-900"
|
class="group cursor-pointer flex items-center px-4 py-3 text-sm text-grey-700 hover:bg-grey-100 hover:text-grey-900"
|
||||||
|
@ -345,8 +347,8 @@
|
||||||
/>
|
/>
|
||||||
Delete
|
Delete
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</MenuItem>
|
||||||
<div role="none">
|
<MenuItem>
|
||||||
<span
|
<span
|
||||||
@click="openForgetModal(props.row)"
|
@click="openForgetModal(props.row)"
|
||||||
class="group cursor-pointer flex items-center px-4 py-3 text-sm text-grey-700 hover:bg-grey-100 hover:text-grey-900"
|
class="group cursor-pointer flex items-center px-4 py-3 text-sm text-grey-700 hover:bg-grey-100 hover:text-grey-900"
|
||||||
|
@ -358,7 +360,7 @@
|
||||||
/>
|
/>
|
||||||
Forget
|
Forget
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</MenuItem>
|
||||||
</more-options>
|
</more-options>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -411,12 +413,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal :open="generateAliasModalOpen" @close="generateAliasModalOpen = false">
|
<Modal :open="generateAliasModalOpen" @close="generateAliasModalOpen = false">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
|
<template v-slot:title> Create new alias </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Create new alias
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
Other aliases e.g. alias@{{ subdomain }} can also be created automatically when they
|
Other aliases e.g. alias@{{ subdomain }} can also be created automatically when they
|
||||||
receive their first email.
|
receive their first email.
|
||||||
|
@ -524,6 +522,8 @@
|
||||||
<multiselect
|
<multiselect
|
||||||
id="alias_recipient_ids"
|
id="alias_recipient_ids"
|
||||||
v-model="generateAliasRecipientIds"
|
v-model="generateAliasRecipientIds"
|
||||||
|
mode="tags"
|
||||||
|
value-prop="id"
|
||||||
:options="recipientOptions"
|
:options="recipientOptions"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:close-on-select="true"
|
:close-on-select="true"
|
||||||
|
@ -533,8 +533,6 @@
|
||||||
placeholder="Select recipient(s) (optional)..."
|
placeholder="Select recipient(s) (optional)..."
|
||||||
label="email"
|
label="email"
|
||||||
track-by="email"
|
track-by="email"
|
||||||
:preselect-first="false"
|
|
||||||
:show-labels="false"
|
|
||||||
>
|
>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
|
|
||||||
|
@ -555,33 +553,29 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="editAliasRecipientsModalOpen" @close="closeAliasRecipientsModal">
|
<Modal :open="editAliasRecipientsModalOpen" @close="closeAliasRecipientsModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
|
<template v-slot:title> Update Alias Recipients </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Update Alias Recipients
|
|
||||||
</h2>
|
|
||||||
<p class="my-4 text-grey-700">
|
<p class="my-4 text-grey-700">
|
||||||
Select the recipients for this alias. You can choose multiple recipients. Leave it empty
|
Select the recipients for this alias. You can choose multiple recipients. Leave it empty
|
||||||
if you would like to use the default recipient.
|
if you would like to use the default recipient.
|
||||||
</p>
|
</p>
|
||||||
<multiselect
|
<multiselect
|
||||||
v-model="aliasRecipientsToEdit"
|
v-model="aliasRecipientsToEdit"
|
||||||
|
mode="tags"
|
||||||
|
value-prop="id"
|
||||||
:options="recipientOptions"
|
:options="recipientOptions"
|
||||||
:multiple="true"
|
:multiple="true"
|
||||||
:close-on-select="true"
|
:close-on-select="true"
|
||||||
:clear-on-select="false"
|
:clear-on-select="false"
|
||||||
:searchable="true"
|
:searchable="true"
|
||||||
:max="10"
|
:max="10"
|
||||||
placeholder="Select recipients"
|
placeholder="Select recipient(s)"
|
||||||
label="email"
|
label="email"
|
||||||
track-by="email"
|
track-by="email"
|
||||||
:preselect-first="false"
|
|
||||||
:show-labels="false"
|
|
||||||
>
|
>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
|
@ -602,16 +596,12 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="restoreAliasModalOpen" @close="closeRestoreModal">
|
<Modal :open="restoreAliasModalOpen" @close="closeRestoreModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
|
<template v-slot:title> Restore alias </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Restore alias
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
Are you sure you want to restore this alias? Once restored it will be
|
Are you sure you want to restore this alias? Once restored it will be
|
||||||
<b>able to receive emails again</b>.
|
<b>able to receive emails again</b>.
|
||||||
|
@ -634,16 +624,12 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="deleteAliasModalOpen" @close="closeDeleteModal">
|
<Modal :open="deleteAliasModalOpen" @close="closeDeleteModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
|
<template v-slot:title> Delete alias </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Delete alias
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
Are you sure you want to delete <b class="break-words">{{ aliasToDelete.email }}</b
|
Are you sure you want to delete <b class="break-words">{{ aliasToDelete.email }}</b
|
||||||
>? You can restore it if you later change your mind. Once deleted,
|
>? You can restore it if you later change your mind. Once deleted,
|
||||||
|
@ -668,16 +654,12 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="forgetAliasModalOpen" @close="closeForgetModal">
|
<Modal :open="forgetAliasModalOpen" @close="closeForgetModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
|
<template v-slot:title> Forget alias </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Forget alias
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
Are you sure you want to forget <b class="break-words">{{ aliasToForget.email }}</b
|
Are you sure you want to forget <b class="break-words">{{ aliasToForget.email }}</b
|
||||||
>? Forgetting an alias will disassociate it from your account.
|
>? Forgetting an alias will disassociate it from your account.
|
||||||
|
@ -705,16 +687,12 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="sendFromAliasModalOpen" @close="closeSendFromModal">
|
<Modal :open="sendFromAliasModalOpen" @close="closeSendFromModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
|
<template v-slot:title> Send from alias </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Send from alias
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
Use this to automatically create the correct address to send an email to in order to send
|
Use this to automatically create the correct address to send an email to in order to send
|
||||||
an <b>email from this alias</b>.
|
an <b>email from this alias</b>.
|
||||||
|
@ -811,7 +789,7 @@
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -824,7 +802,8 @@ import { roundArrow } from 'tippy.js'
|
||||||
import 'tippy.js/dist/svg-arrow.css'
|
import 'tippy.js/dist/svg-arrow.css'
|
||||||
import 'tippy.js/dist/tippy.css'
|
import 'tippy.js/dist/tippy.css'
|
||||||
import tippy from 'tippy.js'
|
import tippy from 'tippy.js'
|
||||||
import Multiselect from 'vue-multiselect'
|
import Multiselect from '@vueform/multiselect'
|
||||||
|
import { MenuItem } from '@headlessui/vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -886,6 +865,7 @@ export default {
|
||||||
Toggle,
|
Toggle,
|
||||||
Multiselect,
|
Multiselect,
|
||||||
MoreOptions,
|
MoreOptions,
|
||||||
|
MenuItem,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -1134,7 +1114,7 @@ export default {
|
||||||
openAliasRecipientsModal(alias) {
|
openAliasRecipientsModal(alias) {
|
||||||
this.editAliasRecipientsModalOpen = true
|
this.editAliasRecipientsModalOpen = true
|
||||||
this.recipientsAliasToEdit = alias
|
this.recipientsAliasToEdit = alias
|
||||||
this.aliasRecipientsToEdit = alias.recipients
|
this.aliasRecipientsToEdit = _.map(alias.recipients, recipient => recipient.id)
|
||||||
},
|
},
|
||||||
closeAliasRecipientsModal() {
|
closeAliasRecipientsModal() {
|
||||||
this.editAliasRecipientsModalOpen = false
|
this.editAliasRecipientsModalOpen = false
|
||||||
|
@ -1149,7 +1129,7 @@ export default {
|
||||||
'/api/v1/alias-recipients',
|
'/api/v1/alias-recipients',
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
alias_id: this.recipientsAliasToEdit.id,
|
alias_id: this.recipientsAliasToEdit.id,
|
||||||
recipient_ids: _.map(this.aliasRecipientsToEdit, recipient => recipient.id),
|
recipient_ids: this.aliasRecipientsToEdit,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
@ -1157,7 +1137,9 @@ export default {
|
||||||
)
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
let alias = _.find(this.rows, ['id', this.recipientsAliasToEdit.id])
|
let alias = _.find(this.rows, ['id', this.recipientsAliasToEdit.id])
|
||||||
alias.recipients = this.aliasRecipientsToEdit
|
alias.recipients = _.filter(this.recipientOptions, recipient =>
|
||||||
|
this.aliasRecipientsToEdit.includes(recipient.id)
|
||||||
|
)
|
||||||
|
|
||||||
this.editAliasRecipientsModalOpen = false
|
this.editAliasRecipientsModalOpen = false
|
||||||
this.editAliasRecipientsLoading = false
|
this.editAliasRecipientsLoading = false
|
||||||
|
@ -1362,4 +1344,4 @@ export default {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
|
<style src="@vueform/multiselect/themes/default.css"></style>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
/>
|
/>
|
||||||
<icon
|
<icon
|
||||||
v-if="search"
|
v-if="search"
|
||||||
@click.native="search = ''"
|
@click="search = ''"
|
||||||
name="close-circle"
|
name="close-circle"
|
||||||
class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
|
class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
|
||||||
/>
|
/>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
|
|
||||||
<vue-good-table
|
<vue-good-table
|
||||||
v-if="initialDomains.length"
|
v-if="initialDomains.length"
|
||||||
@on-search="debounceToolips"
|
v-on:search="debounceToolips"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:rows="rows"
|
:rows="rows"
|
||||||
:search-options="{
|
:search-options="{
|
||||||
|
@ -48,15 +48,15 @@
|
||||||
}"
|
}"
|
||||||
styleClass="vgt-table"
|
styleClass="vgt-table"
|
||||||
>
|
>
|
||||||
<div slot="emptystate" class="flex items-center justify-center h-24 text-lg text-grey-700">
|
<template #emptystate class="flex items-center justify-center h-24 text-lg text-grey-700">
|
||||||
No domains found for that search!
|
No domains found for that search!
|
||||||
</div>
|
</template>
|
||||||
<template slot="table-row" slot-scope="props">
|
<template #table-row="props">
|
||||||
<span
|
<span
|
||||||
v-if="props.column.field == 'created_at'"
|
v-if="props.column.field == 'created_at'"
|
||||||
class="tooltip outline-none text-sm"
|
class="tooltip outline-none text-sm"
|
||||||
:data-tippy-content="rows[props.row.originalIndex].created_at | formatDate"
|
:data-tippy-content="$filters.formatDate(rows[props.row.originalIndex].created_at)"
|
||||||
>{{ props.row.created_at | timeAgo }}
|
>{{ $filters.timeAgo(props.row.created_at) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="props.column.field == 'domain'">
|
<span v-else-if="props.column.field == 'domain'">
|
||||||
<span
|
<span
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
v-clipboard="() => rows[props.row.originalIndex].domain"
|
v-clipboard="() => rows[props.row.originalIndex].domain"
|
||||||
v-clipboard:success="clipboardSuccess"
|
v-clipboard:success="clipboardSuccess"
|
||||||
v-clipboard:error="clipboardError"
|
v-clipboard:error="clipboardError"
|
||||||
>{{ props.row.domain | truncate(30) }}</span
|
>{{ $filters.truncate(props.row.domain, 30) }}</span
|
||||||
>
|
>
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="props.column.field == 'description'">
|
<span v-else-if="props.column.field == 'description'">
|
||||||
|
@ -86,20 +86,20 @@
|
||||||
<icon
|
<icon
|
||||||
name="close"
|
name="close"
|
||||||
class="inline-block w-6 h-6 text-red-300 fill-current cursor-pointer"
|
class="inline-block w-6 h-6 text-red-300 fill-current cursor-pointer"
|
||||||
@click.native="domainIdToEdit = domainDescriptionToEdit = ''"
|
@click="domainIdToEdit = domainDescriptionToEdit = ''"
|
||||||
/>
|
/>
|
||||||
<icon
|
<icon
|
||||||
name="save"
|
name="save"
|
||||||
class="inline-block w-6 h-6 text-cyan-500 fill-current cursor-pointer"
|
class="inline-block w-6 h-6 text-cyan-500 fill-current cursor-pointer"
|
||||||
@click.native="editDomain(rows[props.row.originalIndex])"
|
@click="editDomain(rows[props.row.originalIndex])"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="props.row.description" class="flex items-centers">
|
<div v-else-if="props.row.description" class="flex items-centers">
|
||||||
<span class="outline-none">{{ props.row.description | truncate(60) }}</span>
|
<span class="outline-none">{{ $filters.truncate(props.row.description, 60) }}</span>
|
||||||
<icon
|
<icon
|
||||||
name="edit"
|
name="edit"
|
||||||
class="inline-block w-6 h-6 text-grey-300 fill-current cursor-pointer ml-2"
|
class="inline-block w-6 h-6 text-grey-300 fill-current cursor-pointer ml-2"
|
||||||
@click.native="
|
@click="
|
||||||
;(domainIdToEdit = props.row.id), (domainDescriptionToEdit = props.row.description)
|
;(domainIdToEdit = props.row.id), (domainDescriptionToEdit = props.row.description)
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
|
@ -108,24 +108,24 @@
|
||||||
<icon
|
<icon
|
||||||
name="plus"
|
name="plus"
|
||||||
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native=";(domainIdToEdit = props.row.id), (domainDescriptionToEdit = '')"
|
@click=";(domainIdToEdit = props.row.id), (domainDescriptionToEdit = '')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="props.column.field === 'default_recipient'">
|
<span v-else-if="props.column.field === 'default_recipient'">
|
||||||
<div v-if="props.row.default_recipient">
|
<div v-if="props.row.default_recipient">
|
||||||
{{ props.row.default_recipient.email | truncate(30) }}
|
{{ $filters.truncate(props.row.default_recipient.email, 30) }}
|
||||||
<icon
|
<icon
|
||||||
name="edit"
|
name="edit"
|
||||||
class="ml-2 inline-block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="ml-2 inline-block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="openDomainDefaultRecipientModal(props.row)"
|
@click="openDomainDefaultRecipientModal(props.row)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center" v-else>
|
<div class="flex justify-center" v-else>
|
||||||
<icon
|
<icon
|
||||||
name="plus"
|
name="plus"
|
||||||
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="openDomainDefaultRecipientModal(props.row)"
|
@click="openDomainDefaultRecipientModal(props.row)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
@ -234,7 +234,7 @@
|
||||||
<icon
|
<icon
|
||||||
name="trash"
|
name="trash"
|
||||||
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="openDeleteModal(props.row.id)"
|
@click="openDeleteModal(props.row.id)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -265,12 +265,9 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal :open="addDomainModalOpen" @close="closeCheckRecordsModal">
|
<Modal :open="addDomainModalOpen" @close="closeCheckRecordsModal">
|
||||||
<div v-if="!domainToCheck" class="max-w-2xl w-full bg-white rounded-lg shadow-2xl p-6">
|
<template v-if="!domainToCheck" v-slot:title> Add new domain </template>
|
||||||
<h2
|
<template v-else v-slot:title> Check DNS records </template>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
<template v-if="!domainToCheck" v-slot:content>
|
||||||
>
|
|
||||||
Add new domain
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 mb-2 text-grey-700">
|
<p class="mt-4 mb-2 text-grey-700">
|
||||||
To verify ownership of the domain, please add the following TXT record and then click Add
|
To verify ownership of the domain, please add the following TXT record and then click Add
|
||||||
Domain below.
|
Domain below.
|
||||||
|
@ -315,8 +312,8 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
<div v-else class="max-w-2xl w-full bg-white rounded-lg shadow-2xl p-6">
|
<template v-else v-slot:content>
|
||||||
<h2
|
<h2
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
||||||
>
|
>
|
||||||
|
@ -370,25 +367,22 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="domainDefaultRecipientModalOpen" @close="closeDomainDefaultRecipientModal">
|
<Modal :open="domainDefaultRecipientModalOpen" @close="closeDomainDefaultRecipientModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
|
<template v-slot:title> Update Default Recipient </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Update Default Recipient
|
|
||||||
</h2>
|
|
||||||
<p class="my-4 text-grey-700">
|
<p class="my-4 text-grey-700">
|
||||||
Select the default recipient for this domain. This overrides the default recipient in your
|
Select the default recipient for this domain. This overrides the default recipient in your
|
||||||
account settings. Leave it empty if you would like to use the default recipient in your
|
account settings. Leave it empty if you would like to use the default recipient in your
|
||||||
account settings.
|
account settings.
|
||||||
</p>
|
</p>
|
||||||
<multiselect
|
<multiselect
|
||||||
v-model="defaultRecipient"
|
v-model="defaultRecipientId"
|
||||||
:options="recipientOptions"
|
:options="recipientOptions"
|
||||||
:multiple="false"
|
mode="single"
|
||||||
|
value-prop="id"
|
||||||
:close-on-select="true"
|
:close-on-select="true"
|
||||||
:clear-on-select="false"
|
:clear-on-select="false"
|
||||||
:searchable="false"
|
:searchable="false"
|
||||||
|
@ -396,8 +390,6 @@
|
||||||
placeholder="Select recipient"
|
placeholder="Select recipient"
|
||||||
label="email"
|
label="email"
|
||||||
track-by="email"
|
track-by="email"
|
||||||
:preselect-first="false"
|
|
||||||
:show-labels="false"
|
|
||||||
>
|
>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
|
@ -418,16 +410,12 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="deleteDomainModalOpen" @close="closeDeleteModal">
|
<Modal :open="deleteDomainModalOpen" @close="closeDeleteModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
|
<template v-slot:title> Delete domain </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Delete domain
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
Are you sure you want to delete this domain? This will also delete all aliases associated
|
Are you sure you want to delete this domain? This will also delete all aliases associated
|
||||||
with this domain. You will no longer be able to receive any emails at this domain.
|
with this domain. You will no longer be able to receive any emails at this domain.
|
||||||
|
@ -450,7 +438,7 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -462,7 +450,7 @@ import { roundArrow } from 'tippy.js'
|
||||||
import 'tippy.js/dist/svg-arrow.css'
|
import 'tippy.js/dist/svg-arrow.css'
|
||||||
import 'tippy.js/dist/tippy.css'
|
import 'tippy.js/dist/tippy.css'
|
||||||
import tippy from 'tippy.js'
|
import tippy from 'tippy.js'
|
||||||
import Multiselect from 'vue-multiselect'
|
import Multiselect from '@vueform/multiselect'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -507,7 +495,7 @@ export default {
|
||||||
checkRecordsLoading: false,
|
checkRecordsLoading: false,
|
||||||
domainDefaultRecipientModalOpen: false,
|
domainDefaultRecipientModalOpen: false,
|
||||||
defaultRecipientDomainToEdit: {},
|
defaultRecipientDomainToEdit: {},
|
||||||
defaultRecipient: {},
|
defaultRecipientId: null,
|
||||||
editDefaultRecipientLoading: false,
|
editDefaultRecipientLoading: false,
|
||||||
errors: {},
|
errors: {},
|
||||||
columns: [
|
columns: [
|
||||||
|
@ -667,20 +655,20 @@ export default {
|
||||||
openDomainDefaultRecipientModal(domain) {
|
openDomainDefaultRecipientModal(domain) {
|
||||||
this.domainDefaultRecipientModalOpen = true
|
this.domainDefaultRecipientModalOpen = true
|
||||||
this.defaultRecipientDomainToEdit = domain
|
this.defaultRecipientDomainToEdit = domain
|
||||||
this.defaultRecipient = domain.default_recipient
|
this.defaultRecipientId = domain.default_recipient_id
|
||||||
},
|
},
|
||||||
closeDomainDefaultRecipientModal() {
|
closeDomainDefaultRecipientModal() {
|
||||||
this.domainDefaultRecipientModalOpen = false
|
this.domainDefaultRecipientModalOpen = false
|
||||||
this.defaultRecipientDomainToEdit = {}
|
this.defaultRecipientDomainToEdit = {}
|
||||||
this.defaultRecipient = {}
|
this.defaultRecipientId = null
|
||||||
},
|
},
|
||||||
openCheckRecordsModal(domain) {
|
openCheckRecordsModal(domain) {
|
||||||
this.domainToCheck = domain
|
this.domainToCheck = domain
|
||||||
this.addDomainModalOpen = true
|
this.addDomainModalOpen = true
|
||||||
},
|
},
|
||||||
closeCheckRecordsModal() {
|
closeCheckRecordsModal() {
|
||||||
this.domainToCheck = null
|
|
||||||
this.addDomainModalOpen = false
|
this.addDomainModalOpen = false
|
||||||
|
_.delay(() => (this.domainToCheck = null), 300)
|
||||||
},
|
},
|
||||||
editDomain(domain) {
|
editDomain(domain) {
|
||||||
if (this.domainDescriptionToEdit.length > 200) {
|
if (this.domainDescriptionToEdit.length > 200) {
|
||||||
|
@ -716,7 +704,7 @@ export default {
|
||||||
.patch(
|
.patch(
|
||||||
`/api/v1/domains/${this.defaultRecipientDomainToEdit.id}/default-recipient`,
|
`/api/v1/domains/${this.defaultRecipientDomainToEdit.id}/default-recipient`,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
default_recipient: this.defaultRecipient ? this.defaultRecipient.id : '',
|
default_recipient: this.defaultRecipientId,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
@ -724,17 +712,18 @@ export default {
|
||||||
)
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
let domain = _.find(this.rows, ['id', this.defaultRecipientDomainToEdit.id])
|
let domain = _.find(this.rows, ['id', this.defaultRecipientDomainToEdit.id])
|
||||||
domain.default_recipient = this.defaultRecipient
|
domain.default_recipient = _.find(this.recipientOptions, ['id', this.defaultRecipientId])
|
||||||
|
domain.default_recipient_id = this.defaultRecipientId
|
||||||
|
|
||||||
this.domainDefaultRecipientModalOpen = false
|
this.domainDefaultRecipientModalOpen = false
|
||||||
this.editDefaultRecipientLoading = false
|
this.editDefaultRecipientLoading = false
|
||||||
this.defaultRecipient = {}
|
this.defaultRecipientId = null
|
||||||
this.success("Domain's default recipient updated")
|
this.success("Domain's default recipient updated")
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.domainDefaultRecipientModalOpen = false
|
this.domainDefaultRecipientModalOpen = false
|
||||||
this.editDefaultRecipientLoading = false
|
this.editDefaultRecipientLoading = false
|
||||||
this.defaultRecipient = {}
|
this.defaultRecipientId = null
|
||||||
this.error()
|
this.error()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
/>
|
/>
|
||||||
<icon
|
<icon
|
||||||
v-if="search"
|
v-if="search"
|
||||||
@click.native="search = ''"
|
@click="search = ''"
|
||||||
name="close-circle"
|
name="close-circle"
|
||||||
class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
|
class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
|
||||||
/>
|
/>
|
||||||
|
@ -26,7 +26,7 @@
|
||||||
|
|
||||||
<vue-good-table
|
<vue-good-table
|
||||||
v-if="initialFailedDeliveries.length"
|
v-if="initialFailedDeliveries.length"
|
||||||
@on-search="debounceToolips"
|
v-on:search="debounceToolips"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:rows="rows"
|
:rows="rows"
|
||||||
:search-options="{
|
:search-options="{
|
||||||
|
@ -40,15 +40,15 @@
|
||||||
}"
|
}"
|
||||||
styleClass="vgt-table"
|
styleClass="vgt-table"
|
||||||
>
|
>
|
||||||
<div slot="emptystate" class="flex items-center justify-center h-24 text-lg text-grey-700">
|
<template #emptystate class="flex items-center justify-center h-24 text-lg text-grey-700">
|
||||||
No failed deliveries found for that search!
|
No failed deliveries found for that search!
|
||||||
</div>
|
</template>
|
||||||
<template slot="table-row" slot-scope="props">
|
<template #table-row="props">
|
||||||
<span
|
<span
|
||||||
v-if="props.column.field == 'created_at'"
|
v-if="props.column.field == 'created_at'"
|
||||||
class="tooltip outline-none text-sm"
|
class="tooltip outline-none text-sm"
|
||||||
:data-tippy-content="rows[props.row.originalIndex].created_at | formatDate"
|
:data-tippy-content="$filters.formatDate(rows[props.row.originalIndex].created_at)"
|
||||||
>{{ props.row.created_at | timeAgo }}
|
>{{ $filters.timeAgo(props.row.created_at) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="props.column.field == 'recipient'">
|
<span v-else-if="props.column.field == 'recipient'">
|
||||||
<span
|
<span
|
||||||
|
@ -106,14 +106,14 @@
|
||||||
<span
|
<span
|
||||||
v-else-if="props.column.field == 'attempted_at'"
|
v-else-if="props.column.field == 'attempted_at'"
|
||||||
class="tooltip outline-none text-sm"
|
class="tooltip outline-none text-sm"
|
||||||
:data-tippy-content="rows[props.row.originalIndex].attempted_at | formatDateTime"
|
:data-tippy-content="$filters.formatDateTime(rows[props.row.originalIndex].attempted_at)"
|
||||||
>{{ props.row.attempted_at | timeAgo }}
|
>{{ $filters.timeAgo(props.row.attempted_at) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="flex items-center justify-center outline-none" tabindex="-1">
|
<span v-else class="flex items-center justify-center outline-none" tabindex="-1">
|
||||||
<icon
|
<icon
|
||||||
name="trash"
|
name="trash"
|
||||||
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="openDeleteModal(props.row.id)"
|
@click="openDeleteModal(props.row.id)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -137,12 +137,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal :open="deleteFailedDeliveryModalOpen" @close="closeDeleteModal">
|
<Modal :open="deleteFailedDeliveryModalOpen" @close="closeDeleteModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
|
<template v-slot:title> Delete Failed Delivery </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Delete Failed Delivery
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">Are you sure you want to delete this failed delivery?</p>
|
<p class="mt-4 text-grey-700">Are you sure you want to delete this failed delivery?</p>
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
Failed deliveries are automatically removed when they are more than 3 days old.
|
Failed deliveries are automatically removed when they are more than 3 days old.
|
||||||
|
@ -165,7 +161,7 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
/>
|
/>
|
||||||
<icon
|
<icon
|
||||||
v-if="search"
|
v-if="search"
|
||||||
@click.native="search = ''"
|
@click="search = ''"
|
||||||
name="close-circle"
|
name="close-circle"
|
||||||
class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
|
class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
|
||||||
/>
|
/>
|
||||||
|
@ -33,7 +33,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<vue-good-table
|
<vue-good-table
|
||||||
@on-search="debounceToolips"
|
v-on:search="debounceToolips"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:rows="rows"
|
:rows="rows"
|
||||||
:search-options="{
|
:search-options="{
|
||||||
|
@ -47,10 +47,10 @@
|
||||||
}"
|
}"
|
||||||
styleClass="vgt-table"
|
styleClass="vgt-table"
|
||||||
>
|
>
|
||||||
<div slot="emptystate" class="flex items-center justify-center h-24 text-lg text-grey-700">
|
<template #emptystate class="flex items-center justify-center h-24 text-lg text-grey-700">
|
||||||
No recipients found for that search!
|
No recipients found for that search!
|
||||||
</div>
|
</template>
|
||||||
<template slot="table-column" slot-scope="props">
|
<template #table-column="props">
|
||||||
<span v-if="props.column.label == 'Key'">
|
<span v-if="props.column.label == 'Key'">
|
||||||
Key
|
Key
|
||||||
<span
|
<span
|
||||||
|
@ -82,12 +82,12 @@
|
||||||
{{ props.column.label }}
|
{{ props.column.label }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template slot="table-row" slot-scope="props">
|
<template #table-row="props">
|
||||||
<span
|
<span
|
||||||
v-if="props.column.field == 'created_at'"
|
v-if="props.column.field == 'created_at'"
|
||||||
class="tooltip outline-none text-sm"
|
class="tooltip outline-none text-sm"
|
||||||
:data-tippy-content="rows[props.row.originalIndex].created_at | formatDate"
|
:data-tippy-content="$filters.formatDate(rows[props.row.originalIndex].created_at)"
|
||||||
>{{ props.row.created_at | timeAgo }}
|
>{{ $filters.timeAgo(props.row.created_at) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="props.column.field == 'key'">
|
<span v-else-if="props.column.field == 'key'">
|
||||||
{{ props.row.key }}
|
{{ props.row.key }}
|
||||||
|
@ -99,7 +99,7 @@
|
||||||
v-clipboard="() => rows[props.row.originalIndex].email"
|
v-clipboard="() => rows[props.row.originalIndex].email"
|
||||||
v-clipboard:success="clipboardSuccess"
|
v-clipboard:success="clipboardSuccess"
|
||||||
v-clipboard:error="clipboardError"
|
v-clipboard:error="clipboardError"
|
||||||
>{{ props.row.email | truncate(30) }}</span
|
>{{ $filters.truncate(props.row.email, 30) }}</span
|
||||||
>
|
>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
|
@ -115,7 +115,7 @@
|
||||||
v-if="props.row.aliases.length"
|
v-if="props.row.aliases.length"
|
||||||
class="tooltip outline-none"
|
class="tooltip outline-none"
|
||||||
:data-tippy-content="aliasesTooltip(props.row.aliases, isDefault(props.row.id))"
|
:data-tippy-content="aliasesTooltip(props.row.aliases, isDefault(props.row.id))"
|
||||||
>{{ props.row.aliases[0].email | truncate(40) }}
|
>{{ $filters.truncate(props.row.aliases[0].email, 40) }}
|
||||||
<span
|
<span
|
||||||
v-if="isDefault(props.row.id) && aliasesUsingDefaultCount > 1"
|
v-if="isDefault(props.row.id) && aliasesUsingDefaultCount > 1"
|
||||||
class="block text-grey-500 text-sm"
|
class="block text-grey-500 text-sm"
|
||||||
|
@ -156,7 +156,7 @@
|
||||||
<icon
|
<icon
|
||||||
name="delete"
|
name="delete"
|
||||||
class="tooltip outline-none cursor-pointer block w-6 h-6 text-grey-300 fill-current"
|
class="tooltip outline-none cursor-pointer block w-6 h-6 text-grey-300 fill-current"
|
||||||
@click.native="openDeleteRecipientKeyModal(props.row)"
|
@click="openDeleteRecipientKeyModal(props.row)"
|
||||||
data-tippy-content="Remove public key"
|
data-tippy-content="Remove public key"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
@ -213,19 +213,15 @@
|
||||||
v-if="!isDefault(props.row.id)"
|
v-if="!isDefault(props.row.id)"
|
||||||
name="trash"
|
name="trash"
|
||||||
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="openDeleteModal(props.row)"
|
@click="openDeleteModal(props.row)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</vue-good-table>
|
</vue-good-table>
|
||||||
|
|
||||||
<Modal :open="addRecipientModalOpen" @close="addRecipientModalOpen = false">
|
<Modal :open="addRecipientModalOpen" @close="addRecipientModalOpen = false">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
|
<template v-slot:title> Add new recipient </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Add new recipient
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
Enter the individual email of the new recipient you'd like to add.
|
Enter the individual email of the new recipient you'd like to add.
|
||||||
</p>
|
</p>
|
||||||
|
@ -261,16 +257,12 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="addRecipientKeyModalOpen" @close="closeRecipientKeyModal">
|
<Modal :open="addRecipientKeyModalOpen" @close="closeRecipientKeyModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
|
<template v-slot:title> Add Public GPG Key </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Add Public GPG Key
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">Enter your <b>PUBLIC</b> key data in the text area below.</p>
|
<p class="mt-4 text-grey-700">Enter your <b>PUBLIC</b> key data in the text area below.</p>
|
||||||
<p class="mt-4 text-grey-700">Make sure to remove <b>Comment:</b> and <b>Version:</b></p>
|
<p class="mt-4 text-grey-700">Make sure to remove <b>Comment:</b> and <b>Version:</b></p>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
|
@ -303,16 +295,12 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="deleteRecipientKeyModalOpen" @close="closeDeleteRecipientKeyModal">
|
<Modal :open="deleteRecipientKeyModalOpen" @close="closeDeleteRecipientKeyModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
|
<template v-slot:title> Remove recipient public key </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Remove recipient public key
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
Are you sure you want to remove the public key for this recipient? It will also be removed
|
Are you sure you want to remove the public key for this recipient? It will also be removed
|
||||||
from any other recipients using the same key.
|
from any other recipients using the same key.
|
||||||
|
@ -335,16 +323,12 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="deleteRecipientModalOpen" @close="closeDeleteModal">
|
<Modal :open="deleteRecipientModalOpen" @close="closeDeleteModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
|
<template v-slot:title> Delete recipient </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Delete recipient
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">Are you sure you want to delete this recipient?</p>
|
<p class="mt-4 text-grey-700">Are you sure you want to delete this recipient?</p>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<button
|
<button
|
||||||
|
@ -364,7 +348,7 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -461,6 +445,7 @@ export default {
|
||||||
field: 'should_encrypt',
|
field: 'should_encrypt',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
globalSearchDisabled: true,
|
globalSearchDisabled: true,
|
||||||
|
sortable: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Inline Encryption',
|
label: 'Inline Encryption',
|
||||||
|
|
|
@ -15,32 +15,30 @@
|
||||||
|
|
||||||
<div v-if="initialRules.length" class="bg-white shadow">
|
<div v-if="initialRules.length" class="bg-white shadow">
|
||||||
<draggable
|
<draggable
|
||||||
tag="ul"
|
:component-data="{ name: 'flip-list' }"
|
||||||
|
item-key="id"
|
||||||
v-model="rows"
|
v-model="rows"
|
||||||
v-bind="dragOptions"
|
:group="{ name: 'description' }"
|
||||||
|
ghost-class="ghost"
|
||||||
handle=".handle"
|
handle=".handle"
|
||||||
@change="reorderRules"
|
@change="reorderRules"
|
||||||
>
|
>
|
||||||
<transition-group type="transition" name="flip-list">
|
<template #item="{ element }">
|
||||||
<li
|
<div class="relative flex items-center py-3 px-5 border-b border-grey-100">
|
||||||
class="relative flex items-center py-3 px-5 border-b border-grey-100"
|
|
||||||
v-for="row in rows"
|
|
||||||
:key="row.name"
|
|
||||||
>
|
|
||||||
<div class="flex items-center w-3/5">
|
<div class="flex items-center w-3/5">
|
||||||
<icon
|
<icon
|
||||||
name="menu"
|
name="menu"
|
||||||
class="handle block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="handle block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span class="m-4">{{ row.name }} </span>
|
<span class="m-4">{{ element.name }} </span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="w-1/5 relative flex">
|
<div class="w-1/5 relative flex">
|
||||||
<Toggle
|
<Toggle
|
||||||
v-model="row.active"
|
v-model="element.active"
|
||||||
@on="activateRule(row.id)"
|
@on="activateRule(element.id)"
|
||||||
@off="deactivateRule(row.id)"
|
@off="deactivateRule(element.id)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -48,16 +46,16 @@
|
||||||
<icon
|
<icon
|
||||||
name="edit"
|
name="edit"
|
||||||
class="block w-6 h-6 mr-3 text-grey-300 fill-current cursor-pointer"
|
class="block w-6 h-6 mr-3 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="openEditModal(row)"
|
@click="openEditModal(element)"
|
||||||
/>
|
/>
|
||||||
<icon
|
<icon
|
||||||
name="trash"
|
name="trash"
|
||||||
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="openDeleteModal(row.id)"
|
@click="openDeleteModal(element.id)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</div>
|
||||||
</transition-group>
|
</template>
|
||||||
</draggable>
|
</draggable>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -81,12 +79,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal :open="createRuleModalOpen" @close="createRuleModalOpen = false" :overflow="true">
|
<Modal :open="createRuleModalOpen" @close="createRuleModalOpen = false" :overflow="true">
|
||||||
<div class="max-w-2xl w-full bg-white rounded-lg shadow-2xl p-6 my-12">
|
<template v-slot:title> Create new rule </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Create new rule
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
Rules work on all emails, including replies and also send froms. New conditions and
|
Rules work on all emails, including replies and also send froms. New conditions and
|
||||||
actions will be added over time.
|
actions will be added over time.
|
||||||
|
@ -234,7 +228,7 @@
|
||||||
v-if="createRuleObject.conditions.length > 1"
|
v-if="createRuleObject.conditions.length > 1"
|
||||||
name="trash"
|
name="trash"
|
||||||
class="block ml-4 w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="block ml-4 w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="deleteCondition(createRuleObject, key)"
|
@click="deleteCondition(createRuleObject, key)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -248,7 +242,7 @@
|
||||||
<icon
|
<icon
|
||||||
name="close"
|
name="close"
|
||||||
class="inline-block w-4 h-4 text-grey-900 fill-current cursor-pointer"
|
class="inline-block w-4 h-4 text-grey-900 fill-current cursor-pointer"
|
||||||
@click.native="createRuleObject.conditions[key].values.splice(index, 1)"
|
@click="createRuleObject.conditions[key].values.splice(index, 1)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
@ -377,7 +371,7 @@
|
||||||
v-if="createRuleObject.actions.length > 1"
|
v-if="createRuleObject.actions.length > 1"
|
||||||
name="trash"
|
name="trash"
|
||||||
class="block ml-4 w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="block ml-4 w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="deleteAction(createRuleObject, key)"
|
@click="deleteAction(createRuleObject, key)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -449,16 +443,12 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="editRuleModalOpen" @close="closeEditModal" :overflow="true">
|
<Modal :open="editRuleModalOpen" @close="closeEditModal" :overflow="true">
|
||||||
<div class="max-w-2xl w-full bg-white rounded-lg shadow-2xl p-6 my-12">
|
<template v-slot:title> Edit rule </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Edit rule
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
Rules work on all emails, including replies and also send froms. New conditions and
|
Rules work on all emails, including replies and also send froms. New conditions and
|
||||||
actions will be added over time.
|
actions will be added over time.
|
||||||
|
@ -603,7 +593,7 @@
|
||||||
v-if="editRuleObject.conditions.length > 1"
|
v-if="editRuleObject.conditions.length > 1"
|
||||||
name="trash"
|
name="trash"
|
||||||
class="block ml-4 w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="block ml-4 w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="deleteCondition(editRuleObject, key)"
|
@click="deleteCondition(editRuleObject, key)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -614,7 +604,7 @@
|
||||||
<icon
|
<icon
|
||||||
name="close"
|
name="close"
|
||||||
class="inline-block w-4 h-4 text-grey-900 fill-current cursor-pointer"
|
class="inline-block w-4 h-4 text-grey-900 fill-current cursor-pointer"
|
||||||
@click.native="editRuleObject.conditions[key].values.splice(index, 1)"
|
@click="editRuleObject.conditions[key].values.splice(index, 1)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span
|
<span
|
||||||
|
@ -740,7 +730,7 @@
|
||||||
v-if="editRuleObject.actions.length > 1"
|
v-if="editRuleObject.actions.length > 1"
|
||||||
name="trash"
|
name="trash"
|
||||||
class="block ml-4 w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="block ml-4 w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="deleteAction(editRuleObject, key)"
|
@click="deleteAction(editRuleObject, key)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -812,16 +802,12 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="deleteRuleModalOpen" @close="closeDeleteModal">
|
<Modal :open="deleteRuleModalOpen" @close="closeDeleteModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
|
<template v-slot:title> Delete rule </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Delete rule
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">Are you sure you want to delete this rule?</p>
|
<p class="mt-4 text-grey-700">Are you sure you want to delete this rule?</p>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
<button
|
<button
|
||||||
|
@ -841,7 +827,7 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -945,14 +931,6 @@ export default {
|
||||||
activeRules() {
|
activeRules() {
|
||||||
return _.filter(this.rows, rule => rule.active)
|
return _.filter(this.rows, rule => rule.active)
|
||||||
},
|
},
|
||||||
dragOptions() {
|
|
||||||
return {
|
|
||||||
animation: 0,
|
|
||||||
group: 'description',
|
|
||||||
disabled: false,
|
|
||||||
ghostClass: 'ghost',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rowsIds() {
|
rowsIds() {
|
||||||
return _.map(this.rows, row => row.id)
|
return _.map(this.rows, row => row.id)
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
/>
|
/>
|
||||||
<icon
|
<icon
|
||||||
v-if="search"
|
v-if="search"
|
||||||
@click.native="search = ''"
|
@click="search = ''"
|
||||||
name="close-circle"
|
name="close-circle"
|
||||||
class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
|
class="absolute right-0 inset-y-0 w-5 h-full text-grey-300 fill-current mr-2 flex items-center cursor-pointer"
|
||||||
/>
|
/>
|
||||||
|
@ -34,7 +34,7 @@
|
||||||
|
|
||||||
<vue-good-table
|
<vue-good-table
|
||||||
v-if="initialUsernames.length"
|
v-if="initialUsernames.length"
|
||||||
@on-search="debounceToolips"
|
v-on:search="debounceToolips"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:rows="rows"
|
:rows="rows"
|
||||||
:search-options="{
|
:search-options="{
|
||||||
|
@ -48,15 +48,15 @@
|
||||||
}"
|
}"
|
||||||
styleClass="vgt-table"
|
styleClass="vgt-table"
|
||||||
>
|
>
|
||||||
<div slot="emptystate" class="flex items-center justify-center h-24 text-lg text-grey-700">
|
<template #emptystate class="flex items-center justify-center h-24 text-lg text-grey-700">
|
||||||
No usernames found for that search!
|
No usernames found for that search!
|
||||||
</div>
|
</template>
|
||||||
<template slot="table-row" slot-scope="props">
|
<template #table-row="props">
|
||||||
<span
|
<span
|
||||||
v-if="props.column.field == 'created_at'"
|
v-if="props.column.field == 'created_at'"
|
||||||
class="tooltip outline-none text-sm"
|
class="tooltip outline-none text-sm"
|
||||||
:data-tippy-content="rows[props.row.originalIndex].created_at | formatDate"
|
:data-tippy-content="$filters.formatDate(rows[props.row.originalIndex].created_at)"
|
||||||
>{{ props.row.created_at | timeAgo }}
|
>{{ $filters.timeAgo(props.row.created_at) }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="props.column.field == 'username'">
|
<span v-else-if="props.column.field == 'username'">
|
||||||
<span
|
<span
|
||||||
|
@ -65,7 +65,7 @@
|
||||||
v-clipboard="() => rows[props.row.originalIndex].username"
|
v-clipboard="() => rows[props.row.originalIndex].username"
|
||||||
v-clipboard:success="clipboardSuccess"
|
v-clipboard:success="clipboardSuccess"
|
||||||
v-clipboard:error="clipboardError"
|
v-clipboard:error="clipboardError"
|
||||||
>{{ props.row.username | truncate(30) }}</span
|
>{{ $filters.truncate(props.row.username, 30) }}</span
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
v-if="isDefault(props.row.id)"
|
v-if="isDefault(props.row.id)"
|
||||||
|
@ -92,20 +92,20 @@
|
||||||
<icon
|
<icon
|
||||||
name="close"
|
name="close"
|
||||||
class="inline-block w-6 h-6 text-red-300 fill-current cursor-pointer"
|
class="inline-block w-6 h-6 text-red-300 fill-current cursor-pointer"
|
||||||
@click.native="usernameIdToEdit = usernameDescriptionToEdit = ''"
|
@click="usernameIdToEdit = usernameDescriptionToEdit = ''"
|
||||||
/>
|
/>
|
||||||
<icon
|
<icon
|
||||||
name="save"
|
name="save"
|
||||||
class="inline-block w-6 h-6 text-cyan-500 fill-current cursor-pointer"
|
class="inline-block w-6 h-6 text-cyan-500 fill-current cursor-pointer"
|
||||||
@click.native="editUsername(rows[props.row.originalIndex])"
|
@click="editUsername(rows[props.row.originalIndex])"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="props.row.description" class="flex items-centers">
|
<div v-else-if="props.row.description" class="flex items-centers">
|
||||||
<span class="outline-none">{{ props.row.description | truncate(60) }}</span>
|
<span class="outline-none">{{ $filters.truncate(props.row.description, 60) }}</span>
|
||||||
<icon
|
<icon
|
||||||
name="edit"
|
name="edit"
|
||||||
class="inline-block w-6 h-6 text-grey-300 fill-current cursor-pointer ml-2"
|
class="inline-block w-6 h-6 text-grey-300 fill-current cursor-pointer ml-2"
|
||||||
@click.native="
|
@click="
|
||||||
;(usernameIdToEdit = props.row.id),
|
;(usernameIdToEdit = props.row.id),
|
||||||
(usernameDescriptionToEdit = props.row.description)
|
(usernameDescriptionToEdit = props.row.description)
|
||||||
"
|
"
|
||||||
|
@ -115,24 +115,24 @@
|
||||||
<icon
|
<icon
|
||||||
name="plus"
|
name="plus"
|
||||||
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native=";(usernameIdToEdit = props.row.id), (usernameDescriptionToEdit = '')"
|
@click=";(usernameIdToEdit = props.row.id), (usernameDescriptionToEdit = '')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="props.column.field === 'default_recipient'">
|
<span v-else-if="props.column.field === 'default_recipient'">
|
||||||
<div v-if="props.row.default_recipient">
|
<div v-if="props.row.default_recipient">
|
||||||
{{ props.row.default_recipient.email | truncate(30) }}
|
{{ $filters.truncate(props.row.default_recipient.email, 30) }}
|
||||||
<icon
|
<icon
|
||||||
name="edit"
|
name="edit"
|
||||||
class="ml-2 inline-block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="ml-2 inline-block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="openUsernameDefaultRecipientModal(props.row)"
|
@click="openUsernameDefaultRecipientModal(props.row)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center" v-else>
|
<div class="flex justify-center" v-else>
|
||||||
<icon
|
<icon
|
||||||
name="plus"
|
name="plus"
|
||||||
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="openUsernameDefaultRecipientModal(props.row)"
|
@click="openUsernameDefaultRecipientModal(props.row)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
@ -158,7 +158,7 @@
|
||||||
v-if="!isDefault(props.row.id)"
|
v-if="!isDefault(props.row.id)"
|
||||||
name="trash"
|
name="trash"
|
||||||
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
class="block w-6 h-6 text-grey-300 fill-current cursor-pointer"
|
||||||
@click.native="openDeleteModal(props.row.id)"
|
@click="openDeleteModal(props.row.id)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
@ -187,12 +187,8 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Modal :open="addUsernameModalOpen" @close="addUsernameModalOpen = false">
|
<Modal :open="addUsernameModalOpen" @close="addUsernameModalOpen = false">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
|
<template v-slot:title> Add new username </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Add new username
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
Please choose usernames carefully as you can only add a maximum of
|
Please choose usernames carefully as you can only add a maximum of
|
||||||
{{ usernameCount }}. You can login with any of your usernames.
|
{{ usernameCount }}. You can login with any of your usernames.
|
||||||
|
@ -225,25 +221,22 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="usernameDefaultRecipientModalOpen" @close="closeUsernameDefaultRecipientModal">
|
<Modal :open="usernameDefaultRecipientModalOpen" @close="closeUsernameDefaultRecipientModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl px-6 py-6">
|
<template v-slot:title> Update Default Recipient </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Update Default Recipient
|
|
||||||
</h2>
|
|
||||||
<p class="my-4 text-grey-700">
|
<p class="my-4 text-grey-700">
|
||||||
Select the default recipient for this username. This overrides the default recipient in
|
Select the default recipient for this username. This overrides the default recipient in
|
||||||
your account settings. Leave it empty if you would like to use the default recipient in
|
your account settings. Leave it empty if you would like to use the default recipient in
|
||||||
your account settings.
|
your account settings.
|
||||||
</p>
|
</p>
|
||||||
<multiselect
|
<multiselect
|
||||||
v-model="defaultRecipient"
|
v-model="defaultRecipientId"
|
||||||
:options="recipientOptions"
|
:options="recipientOptions"
|
||||||
:multiple="false"
|
mode="single"
|
||||||
|
value-prop="id"
|
||||||
:close-on-select="true"
|
:close-on-select="true"
|
||||||
:clear-on-select="false"
|
:clear-on-select="false"
|
||||||
:searchable="false"
|
:searchable="false"
|
||||||
|
@ -251,8 +244,6 @@
|
||||||
placeholder="Select recipient"
|
placeholder="Select recipient"
|
||||||
label="email"
|
label="email"
|
||||||
track-by="email"
|
track-by="email"
|
||||||
:preselect-first="false"
|
|
||||||
:show-labels="false"
|
|
||||||
>
|
>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
<div class="mt-6">
|
<div class="mt-6">
|
||||||
|
@ -273,16 +264,12 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
<Modal :open="deleteUsernameModalOpen" @close="closeDeleteModal">
|
<Modal :open="deleteUsernameModalOpen" @close="closeDeleteModal">
|
||||||
<div class="max-w-lg w-full bg-white rounded-lg shadow-2xl p-6">
|
<template v-slot:title> Delete username </template>
|
||||||
<h2
|
<template v-slot:content>
|
||||||
class="font-semibold text-grey-900 text-2xl leading-tight border-b-2 border-grey-100 pb-4"
|
|
||||||
>
|
|
||||||
Delete username
|
|
||||||
</h2>
|
|
||||||
<p class="mt-4 text-grey-700">
|
<p class="mt-4 text-grey-700">
|
||||||
Are you sure you want to delete this username? This will also delete all aliases
|
Are you sure you want to delete this username? This will also delete all aliases
|
||||||
associated with this username. You will no longer be able to receive any emails at this
|
associated with this username. You will no longer be able to receive any emails at this
|
||||||
|
@ -307,7 +294,7 @@
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -319,7 +306,7 @@ import { roundArrow } from 'tippy.js'
|
||||||
import 'tippy.js/dist/svg-arrow.css'
|
import 'tippy.js/dist/svg-arrow.css'
|
||||||
import 'tippy.js/dist/tippy.css'
|
import 'tippy.js/dist/tippy.css'
|
||||||
import tippy from 'tippy.js'
|
import tippy from 'tippy.js'
|
||||||
import Multiselect from 'vue-multiselect'
|
import Multiselect from '@vueform/multiselect'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
|
@ -358,7 +345,7 @@ export default {
|
||||||
deleteUsernameModalOpen: false,
|
deleteUsernameModalOpen: false,
|
||||||
usernameDefaultRecipientModalOpen: false,
|
usernameDefaultRecipientModalOpen: false,
|
||||||
defaultRecipientUsernameToEdit: {},
|
defaultRecipientUsernameToEdit: {},
|
||||||
defaultRecipient: {},
|
defaultRecipientId: null,
|
||||||
editDefaultRecipientLoading: false,
|
editDefaultRecipientLoading: false,
|
||||||
errors: {},
|
errors: {},
|
||||||
columns: [
|
columns: [
|
||||||
|
@ -492,12 +479,12 @@ export default {
|
||||||
openUsernameDefaultRecipientModal(username) {
|
openUsernameDefaultRecipientModal(username) {
|
||||||
this.usernameDefaultRecipientModalOpen = true
|
this.usernameDefaultRecipientModalOpen = true
|
||||||
this.defaultRecipientUsernameToEdit = username
|
this.defaultRecipientUsernameToEdit = username
|
||||||
this.defaultRecipient = username.default_recipient
|
this.defaultRecipientId = username.default_recipient_id
|
||||||
},
|
},
|
||||||
closeUsernameDefaultRecipientModal() {
|
closeUsernameDefaultRecipientModal() {
|
||||||
this.usernameDefaultRecipientModalOpen = false
|
this.usernameDefaultRecipientModalOpen = false
|
||||||
this.defaultRecipientUsernameToEdit = {}
|
this.defaultRecipientUsernameToEdit = {}
|
||||||
this.defaultRecipient = {}
|
this.defaultRecipientId = null
|
||||||
},
|
},
|
||||||
editUsername(username) {
|
editUsername(username) {
|
||||||
if (this.usernameDescriptionToEdit.length > 200) {
|
if (this.usernameDescriptionToEdit.length > 200) {
|
||||||
|
@ -532,7 +519,7 @@ export default {
|
||||||
.patch(
|
.patch(
|
||||||
`/api/v1/usernames/${this.defaultRecipientUsernameToEdit.id}/default-recipient`,
|
`/api/v1/usernames/${this.defaultRecipientUsernameToEdit.id}/default-recipient`,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
default_recipient: this.defaultRecipient ? this.defaultRecipient.id : '',
|
default_recipient: this.defaultRecipientId,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
@ -540,16 +527,21 @@ export default {
|
||||||
)
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
let username = _.find(this.rows, ['id', this.defaultRecipientUsernameToEdit.id])
|
let username = _.find(this.rows, ['id', this.defaultRecipientUsernameToEdit.id])
|
||||||
username.default_recipient = this.defaultRecipient
|
username.default_recipient = _.find(this.recipientOptions, [
|
||||||
|
'id',
|
||||||
|
this.defaultRecipientId,
|
||||||
|
])
|
||||||
|
username.default_recipient_id = this.defaultRecipientId
|
||||||
|
|
||||||
this.usernameDefaultRecipientModalOpen = false
|
this.usernameDefaultRecipientModalOpen = false
|
||||||
this.editDefaultRecipientLoading = false
|
this.editDefaultRecipientLoading = false
|
||||||
this.defaultRecipient = {}
|
this.defaultRecipientId = null
|
||||||
this.success("Username's default recipient updated")
|
this.success("Username's default recipient updated")
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
this.usernameDefaultRecipientModalOpen = false
|
this.usernameDefaultRecipientModalOpen = false
|
||||||
this.editDefaultRecipientLoading = false
|
this.editDefaultRecipientLoading = false
|
||||||
this.defaultRecipient = {}
|
this.defaultRecipientId = null
|
||||||
this.error()
|
this.error()
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
|
@ -41,6 +41,11 @@
|
||||||
{{ $errors->first('username') }}
|
{{ $errors->first('username') }}
|
||||||
</p>
|
</p>
|
||||||
@endif
|
@endif
|
||||||
|
@if ($errors->has('id'))
|
||||||
|
<p class="text-red-500 text-xs italic mt-4">
|
||||||
|
{{ $errors->first('id') }}
|
||||||
|
</p>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex flex-wrap mb-2">
|
<div class="flex flex-wrap mb-2">
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
<div class="container py-8">
|
<div class="container py-8">
|
||||||
@include('shared.status')
|
@include('shared.status')
|
||||||
|
|
||||||
<domains :initial-domains="{{json_encode($domains)}}" domain-name="{{config('anonaddy.domain')}}" hostname="{{config('anonaddy.hostname')}}" :recipient-options="{{ json_encode(Auth::user()->verifiedRecipients) }}" aa-verify="{{ sha1(config('anonaddy.secret') . Auth::user()->id . Auth::user()->domains->count()) }}" />
|
<domains :initial-domains="{{json_encode($domains)}}" domain-name="{{config('anonaddy.domain')}}" hostname="{{config('anonaddy.hostname')}}" :recipient-options="{{ json_encode(Auth::user()->verifiedRecipients()->select(['id', 'email'])->get()) }}" aa-verify="{{ sha1(config('anonaddy.secret') . Auth::user()->id . Auth::user()->domains->count()) }}" />
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
|
@ -27,7 +27,6 @@
|
||||||
|
|
||||||
@yield('content')
|
@yield('content')
|
||||||
|
|
||||||
<portal-target name="modals"></portal-target>
|
|
||||||
<notifications position="bottom right" />
|
<notifications position="bottom right" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,6 @@
|
||||||
<div class="container py-8">
|
<div class="container py-8">
|
||||||
@include('shared.status')
|
@include('shared.status')
|
||||||
|
|
||||||
<usernames :user="{{json_encode(Auth::user())}}" :initial-usernames="{{json_encode($usernames)}}" :username-count="{{config('anonaddy.additional_username_limit')}}" :recipient-options="{{ json_encode(Auth::user()->verifiedRecipients) }}" />
|
<usernames :user="{{json_encode(Auth::user())}}" :initial-usernames="{{json_encode($usernames)}}" :username-count="{{config('anonaddy.additional_username_limit')}}" :recipient-options="{{ json_encode(Auth::user()->verifiedRecipients()->select(['id', 'email'])->get()) }}" />
|
||||||
</div>
|
</div>
|
||||||
@endsection
|
@endsection
|
|
@ -8,6 +8,7 @@ use App\Http\Controllers\Api\ActiveUsernameController;
|
||||||
use App\Http\Controllers\Api\AliasController;
|
use App\Http\Controllers\Api\AliasController;
|
||||||
use App\Http\Controllers\Api\AliasRecipientController;
|
use App\Http\Controllers\Api\AliasRecipientController;
|
||||||
use App\Http\Controllers\Api\AllowedRecipientController;
|
use App\Http\Controllers\Api\AllowedRecipientController;
|
||||||
|
use App\Http\Controllers\Api\ApiTokenDetailController;
|
||||||
use App\Http\Controllers\Api\AppVersionController;
|
use App\Http\Controllers\Api\AppVersionController;
|
||||||
use App\Http\Controllers\Api\CatchAllDomainController;
|
use App\Http\Controllers\Api\CatchAllDomainController;
|
||||||
use App\Http\Controllers\Api\CatchAllUsernameController;
|
use App\Http\Controllers\Api\CatchAllUsernameController;
|
||||||
|
@ -156,4 +157,6 @@ Route::group([
|
||||||
Route::get('/account-details', [AccountDetailController::class, 'index']);
|
Route::get('/account-details', [AccountDetailController::class, 'index']);
|
||||||
|
|
||||||
Route::get('/app-version', [AppVersionController::class, 'index']);
|
Route::get('/app-version', [AppVersionController::class, 'index']);
|
||||||
|
|
||||||
|
Route::get('api-token-details', [ApiTokenDetailController::class, 'show']);
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Http\Controllers\AliasExportController;
|
use App\Http\Controllers\AliasExportController;
|
||||||
|
use App\Http\Controllers\Auth\ApiAuthenticationController;
|
||||||
use App\Http\Controllers\Auth\BackupCodeController;
|
use App\Http\Controllers\Auth\BackupCodeController;
|
||||||
use App\Http\Controllers\Auth\ForgotUsernameController;
|
use App\Http\Controllers\Auth\ForgotUsernameController;
|
||||||
use App\Http\Controllers\Auth\PersonalAccessTokenController;
|
use App\Http\Controllers\Auth\PersonalAccessTokenController;
|
||||||
|
@ -42,13 +43,16 @@ use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
Auth::routes(['verify' => true, 'register' => config('anonaddy.enable_registration')]);
|
Auth::routes(['verify' => true, 'register' => config('anonaddy.enable_registration')]);
|
||||||
|
|
||||||
|
// Get API access token
|
||||||
|
Route::post('api/auth/login', [ApiAuthenticationController::class, 'login']);
|
||||||
|
Route::post('api/auth/mfa', [ApiAuthenticationController::class, 'mfa']);
|
||||||
|
|
||||||
Route::controller(ForgotUsernameController::class)->group(function () {
|
Route::controller(ForgotUsernameController::class)->group(function () {
|
||||||
Route::get('/username/reminder', 'show')->name('username.reminder.show');
|
Route::get('/username/reminder', 'show')->name('username.reminder.show');
|
||||||
Route::post('/username/email', 'sendReminderEmail')->name('username.email');
|
Route::post('/username/email', 'sendReminderEmail')->name('username.email');
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::post('/login/2fa', [TwoFactorAuthController::class, 'authenticateTwoFactor'])->name('login.2fa')->middleware(['2fa', 'throttle', 'auth']);
|
Route::post('/login/2fa', [TwoFactorAuthController::class, 'authenticateTwoFactor'])->name('login.2fa')->middleware(['2fa', 'throttle:3,1', 'auth']);
|
||||||
|
|
||||||
Route::controller(BackupCodeController::class)->group(function () {
|
Route::controller(BackupCodeController::class)->group(function () {
|
||||||
Route::get('/login/backup-code', 'index')->name('login.backup_code.index');
|
Route::get('/login/backup-code', 'index')->name('login.backup_code.index');
|
||||||
|
|
|
@ -297,8 +297,6 @@ class AliasesTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function user_can_forget_alias()
|
public function user_can_forget_alias()
|
||||||
{
|
{
|
||||||
$this->withoutExceptionHandling();
|
|
||||||
|
|
||||||
$alias = Alias::factory()->create([
|
$alias = Alias::factory()->create([
|
||||||
'user_id' => $this->user->id
|
'user_id' => $this->user->id
|
||||||
]);
|
]);
|
||||||
|
@ -316,8 +314,6 @@ class AliasesTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function user_can_forget_shared_domain_alias()
|
public function user_can_forget_shared_domain_alias()
|
||||||
{
|
{
|
||||||
$this->withoutExceptionHandling();
|
|
||||||
|
|
||||||
$sharedDomainAlias = Alias::factory()->create([
|
$sharedDomainAlias = Alias::factory()->create([
|
||||||
'user_id' => $this->user->id,
|
'user_id' => $this->user->id,
|
||||||
'domain' => 'anonaddy.me',
|
'domain' => 'anonaddy.me',
|
||||||
|
|
34
tests/Feature/Api/ApiTokenDetailsTest.php
Normal file
34
tests/Feature/Api/ApiTokenDetailsTest.php
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature\Api;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class ApiTokenDetailsTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function user_can_get_account_details()
|
||||||
|
{
|
||||||
|
$user = User::factory()->create();
|
||||||
|
$token = $user->createToken('New');
|
||||||
|
|
||||||
|
$response = $this->withHeaders([
|
||||||
|
'Authorization' => 'Bearer ' . $token->plainTextToken,
|
||||||
|
])->json('GET', '/api/v1/api-token-details');
|
||||||
|
|
||||||
|
$response->assertSuccessful();
|
||||||
|
|
||||||
|
$this->assertEquals($token->accessToken->name, $response->json()['name']);
|
||||||
|
$this->assertEquals($token->accessToken->created_at, $response->json()['created_at']);
|
||||||
|
$this->assertEquals($token->accessToken->expires_at, $response->json()['expires_at']);
|
||||||
|
}
|
||||||
|
}
|
178
tests/Feature/ApiAuthenticationTest.php
Normal file
178
tests/Feature/ApiAuthenticationTest.php
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Feature;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Illuminate\Routing\Middleware\ThrottleRequestsWithRedis;
|
||||||
|
use Illuminate\Support\Facades\Hash;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class ApiAuthenticationTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
protected $user;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->user = User::factory()->create([
|
||||||
|
'password' => Hash::make('mypassword')
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->user->usernames()->save($this->user->defaultUsername);
|
||||||
|
$this->user->defaultUsername->username = 'johndoe';
|
||||||
|
$this->user->defaultUsername->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function user_can_retreive_valid_access_token()
|
||||||
|
{
|
||||||
|
$this->withoutMiddleware(ThrottleRequestsWithRedis::class);
|
||||||
|
|
||||||
|
$response = $this->json('POST', '/api/auth/login', [
|
||||||
|
'username' => 'johndoe',
|
||||||
|
'password' => 'mypassword',
|
||||||
|
'device_name' => 'Firefox'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertSuccessful();
|
||||||
|
$this->assertEquals($this->user->tokens[0]->token, hash('sha256', $response->json()['api_key']));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function user_password_must_be_correct_to_get_access_token()
|
||||||
|
{
|
||||||
|
$this->withoutMiddleware(ThrottleRequestsWithRedis::class);
|
||||||
|
|
||||||
|
$response = $this->json('POST', '/api/auth/login', [
|
||||||
|
'username' => 'johndoe',
|
||||||
|
'password' => 'myincorrectpassword',
|
||||||
|
'device_name' => 'Firefox'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertUnauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function user_must_exist_to_get_access_token()
|
||||||
|
{
|
||||||
|
$this->withoutMiddleware(ThrottleRequestsWithRedis::class);
|
||||||
|
|
||||||
|
$response = $this->json('POST', '/api/auth/login', [
|
||||||
|
'username' => 'doesnotexist',
|
||||||
|
'password' => 'mypassword',
|
||||||
|
'device_name' => 'Firefox'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertUnauthorized();
|
||||||
|
$response->assertExactJson(['error' => 'The provided credentials are incorrect']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function user_is_throttled_by_middleware_for_too_many_requests()
|
||||||
|
{
|
||||||
|
$this->json('POST', '/api/auth/login', [
|
||||||
|
'username' => 'johndoe',
|
||||||
|
'password' => 'incorrect1',
|
||||||
|
'device_name' => 'Firefox'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->json('POST', '/api/auth/login', [
|
||||||
|
'username' => 'johndoe',
|
||||||
|
'password' => 'incorrect2',
|
||||||
|
'device_name' => 'Firefox'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->json('POST', '/api/auth/login', [
|
||||||
|
'username' => 'johndoe',
|
||||||
|
'password' => 'incorrect3',
|
||||||
|
'device_name' => 'Firefox'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->json('POST', '/api/auth/login', [
|
||||||
|
'username' => 'johndoe',
|
||||||
|
'password' => 'incorrect4',
|
||||||
|
'device_name' => 'Firefox'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(429);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function user_cannot_get_access_token_with_webauthn_enabled()
|
||||||
|
{
|
||||||
|
$this->withoutMiddleware(ThrottleRequestsWithRedis::class);
|
||||||
|
|
||||||
|
$this->user->webauthnKeys()->create([
|
||||||
|
'name' => 'key',
|
||||||
|
'enabled' => true,
|
||||||
|
'credentialId' => 'xyz',
|
||||||
|
'type' => 'public-key',
|
||||||
|
'transports' => [],
|
||||||
|
'attestationType' => 'none',
|
||||||
|
'trustPath' => '{"type":"Webauthn\\TrustPath\\EmptyTrustPath"}',
|
||||||
|
'aaguid' => '00000000-0000-0000-0000-000000000000',
|
||||||
|
'credentialPublicKey' => 'xyz',
|
||||||
|
'counter' => 0
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->json('POST', '/api/auth/login', [
|
||||||
|
'username' => 'johndoe',
|
||||||
|
'password' => 'mypassword',
|
||||||
|
'device_name' => 'Firefox'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertForbidden();
|
||||||
|
$response->assertExactJson(['error' => 'WebAuthn authentication is not currently supported from the extension or mobile apps, please use an API key to login instead']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @test */
|
||||||
|
public function user_must_provide_correct_otp_if_enabled()
|
||||||
|
{
|
||||||
|
$this->withoutMiddleware(ThrottleRequestsWithRedis::class);
|
||||||
|
|
||||||
|
$secret = app('pragmarx.google2fa')->generateSecretKey();
|
||||||
|
$this->user->update([
|
||||||
|
'two_factor_secret' => $secret,
|
||||||
|
'two_factor_enabled' => true
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->json('POST', '/api/auth/login', [
|
||||||
|
'username' => 'johndoe',
|
||||||
|
'password' => 'mypassword',
|
||||||
|
'device_name' => 'Firefox'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response->assertStatus(422);
|
||||||
|
|
||||||
|
$mfaKey = $response->json()['mfa_key'];
|
||||||
|
$csrfToken = $response->json()['csrf_token'];
|
||||||
|
$this->assertNotNull($mfaKey);
|
||||||
|
$this->assertNotNull($csrfToken);
|
||||||
|
|
||||||
|
$response2 = $this->withHeaders([
|
||||||
|
'X-CSRF-TOKEN' => $csrfToken,
|
||||||
|
])->json('POST', '/api/auth/mfa', [
|
||||||
|
'mfa_key' => $mfaKey,
|
||||||
|
'otp' => '000000',
|
||||||
|
'device_name' => 'Firefox'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response2->assertUnauthorized();
|
||||||
|
$response2->assertExactJson(['error' => 'The \'One Time Password\' typed was wrong']);
|
||||||
|
|
||||||
|
$response3 = $this->withHeaders([
|
||||||
|
'X-CSRF-TOKEN' => $csrfToken,
|
||||||
|
])->json('POST', '/api/auth/mfa', [
|
||||||
|
'mfa_key' => $mfaKey,
|
||||||
|
'otp' => app('pragmarx.google2fa')->getCurrentOtp($secret),
|
||||||
|
'device_name' => 'Firefox'
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response3->assertSuccessful();
|
||||||
|
$this->assertEquals($this->user->tokens[0]->token, hash('sha256', $response3->json()['api_key']));
|
||||||
|
}
|
||||||
|
}
|
|
@ -99,6 +99,8 @@ class LoginTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function user_can_receive_username_reminder_email()
|
public function user_can_receive_username_reminder_email()
|
||||||
{
|
{
|
||||||
|
$this->withoutMiddleware();
|
||||||
|
|
||||||
Notification::fake();
|
Notification::fake();
|
||||||
|
|
||||||
$recipient = $this->user->recipients[0];
|
$recipient = $this->user->recipients[0];
|
||||||
|
@ -116,6 +118,8 @@ class LoginTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function username_reminder_email_not_sent_for_unkown_email()
|
public function username_reminder_email_not_sent_for_unkown_email()
|
||||||
{
|
{
|
||||||
|
$this->withoutMiddleware();
|
||||||
|
|
||||||
Notification::fake();
|
Notification::fake();
|
||||||
|
|
||||||
$this->post('/username/email', [
|
$this->post('/username/email', [
|
||||||
|
|
|
@ -28,7 +28,6 @@ class ShowFailedDeliveriesTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function user_can_view_failed_deliveries_from_the_failed_deliveries_page()
|
public function user_can_view_failed_deliveries_from_the_failed_deliveries_page()
|
||||||
{
|
{
|
||||||
$this->withoutExceptionHandling();
|
|
||||||
$failedDeliveries = FailedDelivery::factory()->count(3)->create([
|
$failedDeliveries = FailedDelivery::factory()->count(3)->create([
|
||||||
'user_id' => $this->user->id
|
'user_id' => $this->user->id
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -32,7 +32,6 @@ class ShowRecipientsTest extends TestCase
|
||||||
/** @test */
|
/** @test */
|
||||||
public function user_can_view_recipients_from_the_recipients_page()
|
public function user_can_view_recipients_from_the_recipients_page()
|
||||||
{
|
{
|
||||||
$this->withoutExceptionHandling();
|
|
||||||
$recipients = Recipient::factory()->count(5)->create([
|
$recipients = Recipient::factory()->count(5)->create([
|
||||||
'user_id' => $this->user->id
|
'user_id' => $this->user->id
|
||||||
]);
|
]);
|
||||||
|
|
Loading…
Add table
Reference in a new issue