Feat: Add unlimited flag to app roles and enhance Keka integration
- Introduced `is_unlimited` field in `app_roles` table to support roles with unlimited duration. - Enhanced front-end and back-end implementations to manage unlimited roles, including UI updates and validation. - Refactored `KekaOutlookController` and `KekaOutlookService` for streamlined exception handling and code consistency. - Applied linting and formatting improvements across updated files.
This commit is contained in:
parent
14a3339b3c
commit
dfb47f7d19
@ -12,6 +12,7 @@ public function __construct(
|
|||||||
public int $roleId,
|
public int $roleId,
|
||||||
public string $validUpto,
|
public string $validUpto,
|
||||||
/** @var int[] */
|
/** @var int[] */
|
||||||
public array $appIds
|
public array $appIds,
|
||||||
|
public bool $isUnlimited = false,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ public function __construct(
|
|||||||
public int $appId,
|
public int $appId,
|
||||||
public string $appName,
|
public string $appName,
|
||||||
public string $duration,
|
public string $duration,
|
||||||
|
public bool $isUnlimited = false,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -5,8 +5,8 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Services\KekaOutlookService;
|
use App\Services\KekaOutlookService;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\{RedirectResponse, Request};
|
||||||
use Illuminate\Http\Request;
|
use RuntimeException;
|
||||||
|
|
||||||
final class KekaOutlookController extends Controller
|
final class KekaOutlookController extends Controller
|
||||||
{
|
{
|
||||||
@ -31,7 +31,7 @@ public function connect(Request $request): RedirectResponse
|
|||||||
if ($this->kekaOutlook->isConnected($user)) {
|
if ($this->kekaOutlook->isConnected($user)) {
|
||||||
try {
|
try {
|
||||||
$this->kekaOutlook->getValidToken($user);
|
$this->kekaOutlook->getValidToken($user);
|
||||||
} catch (\RuntimeException) {
|
} catch (RuntimeException) {
|
||||||
// Token fully expired/revoked → re-authenticate
|
// Token fully expired/revoked → re-authenticate
|
||||||
return redirect()->away($this->kekaOutlook->getAuthUrl());
|
return redirect()->away($this->kekaOutlook->getAuthUrl());
|
||||||
}
|
}
|
||||||
@ -54,7 +54,7 @@ public function callback(Request $request): RedirectResponse
|
|||||||
{
|
{
|
||||||
if ($request->filled('error')) {
|
if ($request->filled('error')) {
|
||||||
return redirect()->route('dashboard')
|
return redirect()->route('dashboard')
|
||||||
->with('error', 'Microsoft login failed: ' . $request->get('error_description', $request->get('error')));
|
->with('error', 'Microsoft login failed: '.$request->get('error_description', $request->get('error')));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (! $this->kekaOutlook->isValidState($request->get('state'))) {
|
if (! $this->kekaOutlook->isValidState($request->get('state'))) {
|
||||||
@ -73,7 +73,7 @@ public function callback(Request $request): RedirectResponse
|
|||||||
|
|
||||||
if (! $result['success']) {
|
if (! $result['success']) {
|
||||||
return redirect()->route('dashboard')
|
return redirect()->route('dashboard')
|
||||||
->with('error', 'Connection failed: ' . ($result['message'] ?? 'Unknown error'));
|
->with('error', 'Connection failed: '.($result['message'] ?? 'Unknown error'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token stored ✓ → Keka SSO will auto-login via active Microsoft session
|
// Token stored ✓ → Keka SSO will auto-login via active Microsoft session
|
||||||
|
|||||||
@ -14,7 +14,7 @@
|
|||||||
* @property int $role_id
|
* @property int $role_id
|
||||||
* @property string $duration Validity of the role
|
* @property string $duration Validity of the role
|
||||||
*/
|
*/
|
||||||
#[Fillable(['app_id', 'role_id', 'duration'])]
|
#[Fillable(['app_id', 'role_id', 'duration', 'is_unlimited'])]
|
||||||
class AppRole extends Pivot
|
class AppRole extends Pivot
|
||||||
{
|
{
|
||||||
public $timestamps = false;
|
public $timestamps = false;
|
||||||
@ -36,4 +36,11 @@ public function role(): BelongsTo
|
|||||||
{
|
{
|
||||||
return $this->belongsTo(Role::class, 'role_id');
|
return $this->belongsTo(Role::class, 'role_id');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function casts(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'is_unlimited' => 'boolean',
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,17 +5,23 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||||
|
use Carbon\CarbonImmutable;
|
||||||
use Database\Factories\UserFactory;
|
use Database\Factories\UserFactory;
|
||||||
|
use DateTime;
|
||||||
use Illuminate\Database\Eloquent\Attributes\{Fillable, Hidden};
|
use Illuminate\Database\Eloquent\Attributes\{Fillable, Hidden};
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\{BelongsToMany, HasMany};
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||||
|
use Spatie\Permission\Models\Permission;
|
||||||
use Spatie\Permission\Traits\HasRoles;
|
use Spatie\Permission\Traits\HasRoles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property DateTime|CarbonImmutable|null $keka_ms_token_expires_at
|
||||||
|
*/
|
||||||
#[Fillable(['name', 'email', 'password', 'immutable_id'])]
|
#[Fillable(['name', 'email', 'password', 'immutable_id'])]
|
||||||
#[Hidden(['password', 'two_factor_secret', 'two_factor_recovery_codes', 'remember_token'])]
|
#[Hidden(['password', 'two_factor_secret', 'two_factor_recovery_codes', 'remember_token'])]
|
||||||
final class User extends Authenticatable
|
final class User extends Authenticatable
|
||||||
@ -27,10 +33,10 @@ final class User extends Authenticatable
|
|||||||
use Notifiable;
|
use Notifiable;
|
||||||
use TwoFactorAuthenticatable;
|
use TwoFactorAuthenticatable;
|
||||||
|
|
||||||
public function restrictedPermissions(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
public function restrictedPermissions(): BelongsToMany
|
||||||
{
|
{
|
||||||
return $this->belongsToMany(
|
return $this->belongsToMany(
|
||||||
\Spatie\Permission\Models\Permission::class,
|
Permission::class,
|
||||||
'restricted_user_permissions',
|
'restricted_user_permissions',
|
||||||
'user_id',
|
'user_id',
|
||||||
'permission_id'
|
'permission_id'
|
||||||
@ -49,6 +55,22 @@ public function initials(): string
|
|||||||
->implode('');
|
->implode('');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the OAuth token for a specific provider.
|
||||||
|
*/
|
||||||
|
public function oauthToken(string $provider): ?OauthToken
|
||||||
|
{
|
||||||
|
return $this->oauthTokens()->forProvider($provider)->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return HasMany<OauthToken, $this>
|
||||||
|
*/
|
||||||
|
public function oauthTokens(): HasMany
|
||||||
|
{
|
||||||
|
return $this->hasMany(OauthToken::class);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the attributes that should be cast.
|
* Get the attributes that should be cast.
|
||||||
*
|
*
|
||||||
@ -62,20 +84,4 @@ protected function casts(): array
|
|||||||
'keka_ms_token_expires_at' => 'datetime',
|
'keka_ms_token_expires_at' => 'datetime',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return HasMany<OauthToken, $this>
|
|
||||||
*/
|
|
||||||
public function oauthTokens(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(OauthToken::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the OAuth token for a specific provider.
|
|
||||||
*/
|
|
||||||
public function oauthToken(string $provider): ?OauthToken
|
|
||||||
{
|
|
||||||
return $this->oauthTokens()->forProvider($provider)->first();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,12 +39,13 @@ public function assign(AssignAppsToRoleData $data): void
|
|||||||
'role_id' => $data->roleId,
|
'role_id' => $data->roleId,
|
||||||
'app_id' => $appId,
|
'app_id' => $appId,
|
||||||
'duration' => $data->validUpto,
|
'duration' => $data->validUpto,
|
||||||
|
'is_unlimited' => $data->isUnlimited,
|
||||||
], $data->appIds);
|
], $data->appIds);
|
||||||
|
|
||||||
AppRole::query()->upsert(
|
AppRole::query()->upsert(
|
||||||
$records,
|
$records,
|
||||||
['role_id', 'app_id'],
|
['role_id', 'app_id'],
|
||||||
['duration']
|
['duration', 'is_unlimited'],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -123,7 +124,7 @@ public function getAppsForRole(int $roleId): DataCollection
|
|||||||
->with('app:id,name')
|
->with('app:id,name')
|
||||||
->where('role_id', $roleId)
|
->where('role_id', $roleId)
|
||||||
->orderBy('id')
|
->orderBy('id')
|
||||||
->get(['id', 'app_id', 'role_id', 'duration']);
|
->get(['id', 'app_id', 'role_id', 'duration', 'is_unlimited']);
|
||||||
|
|
||||||
return RoleAppData::collect(
|
return RoleAppData::collect(
|
||||||
$assignments->map(fn (AppRole $row): array => [
|
$assignments->map(fn (AppRole $row): array => [
|
||||||
@ -131,6 +132,7 @@ public function getAppsForRole(int $roleId): DataCollection
|
|||||||
'appId' => $row->app_id,
|
'appId' => $row->app_id,
|
||||||
'appName' => $row->app->name ?? '',
|
'appName' => $row->app->name ?? '',
|
||||||
'duration' => $row->duration,
|
'duration' => $row->duration,
|
||||||
|
'isUnlimited' => $row->is_unlimited,
|
||||||
])->all(),
|
])->all(),
|
||||||
DataCollection::class,
|
DataCollection::class,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use Illuminate\Support\Facades\Http;
|
use Illuminate\Support\Facades\Http;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles Microsoft OAuth (delegated, authorization code flow) for Keka-Outlook integration.
|
* Handles Microsoft OAuth (delegated, authorization code flow) for Keka-Outlook integration.
|
||||||
@ -145,24 +146,16 @@ private function storeTokens(User $user, array $tokenData): void
|
|||||||
$user->save();
|
$user->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the user has connected Keka/Outlook (has a refresh token stored).
|
|
||||||
*/
|
|
||||||
public function isConnected(User $user): bool
|
|
||||||
{
|
|
||||||
return ! empty($user->keka_ms_refresh_token);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a valid access token for the user.
|
* Get a valid access token for the user.
|
||||||
* Silently refreshes via refresh_token if the current access token has expired.
|
* Silently refreshes via refresh_token if the current access token has expired.
|
||||||
*
|
*
|
||||||
* @throws \RuntimeException if the user has not connected or refresh fails.
|
* @throws RuntimeException if the user has not connected or refresh fails.
|
||||||
*/
|
*/
|
||||||
public function getValidToken(User $user): string
|
public function getValidToken(User $user): string
|
||||||
{
|
{
|
||||||
if (! $this->isConnected($user)) {
|
if (! $this->isConnected($user)) {
|
||||||
throw new \RuntimeException('User has not connected Keka/Outlook.');
|
throw new RuntimeException('User has not connected Keka/Outlook.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Still valid? (1 minute buffer)
|
// Still valid? (1 minute buffer)
|
||||||
@ -177,10 +170,18 @@ public function getValidToken(User $user): string
|
|||||||
return $this->refreshToken($user);
|
return $this->refreshToken($user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the user has connected Keka/Outlook (has a refresh token stored).
|
||||||
|
*/
|
||||||
|
public function isConnected(User $user): bool
|
||||||
|
{
|
||||||
|
return ! empty($user->keka_ms_refresh_token);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use the stored refresh_token to obtain a new access_token silently.
|
* Use the stored refresh_token to obtain a new access_token silently.
|
||||||
*
|
*
|
||||||
* @throws \RuntimeException if Microsoft rejects the refresh token (user must reconnect).
|
* @throws RuntimeException if Microsoft rejects the refresh token (user must reconnect).
|
||||||
*/
|
*/
|
||||||
private function refreshToken(User $user): string
|
private function refreshToken(User $user): string
|
||||||
{
|
{
|
||||||
@ -199,7 +200,7 @@ private function refreshToken(User $user): string
|
|||||||
// Refresh token expired/revoked -> force user to reconnect.
|
// Refresh token expired/revoked -> force user to reconnect.
|
||||||
$this->disconnect($user);
|
$this->disconnect($user);
|
||||||
|
|
||||||
throw new \RuntimeException(
|
throw new RuntimeException(
|
||||||
$response->json('error_description', 'Session expired. Please reconnect Keka.')
|
$response->json('error_description', 'Session expired. Please reconnect Keka.')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'client_id' => env('MICROSOFT_GRAPH_CLIENT_ID'),
|
'client_id' => env('MICROSOFT_GRAPH_CLIENT_ID'),
|
||||||
'client_secret' => env('MICROSOFT_GRAPH_CLIENT_SECRET'),
|
'client_secret' => env('MICROSOFT_GRAPH_CLIENT_SECRET'),
|
||||||
@ -11,7 +13,7 @@
|
|||||||
// After first-time login: Keka's own Microsoft SSO URL
|
// After first-time login: Keka's own Microsoft SSO URL
|
||||||
// After already connected: Keka dashboard
|
// After already connected: Keka dashboard
|
||||||
'keka_login_url' => env('KEKA_LOGIN_URL', 'https://sentientgeeks.keka.com/'),
|
'keka_login_url' => env('KEKA_LOGIN_URL', 'https://sentientgeeks.keka.com/'),
|
||||||
'keka_dashboard_url'=> env('KEKA_DASHBOARD_URL', 'https://sentientgeeks.keka.com/'),
|
'keka_dashboard_url' => env('KEKA_DASHBOARD_URL', 'https://sentientgeeks.keka.com/'),
|
||||||
|
|
||||||
'keka_scopes' => [
|
'keka_scopes' => [
|
||||||
'openid',
|
'openid',
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration
|
return new class() extends Migration
|
||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('users', function (Blueprint $table) {
|
Schema::table('users', function (Blueprint $table): void {
|
||||||
$table->text('keka_ms_access_token')->nullable();
|
$table->text('keka_ms_access_token')->nullable();
|
||||||
$table->text('keka_ms_refresh_token')->nullable();
|
$table->text('keka_ms_refresh_token')->nullable();
|
||||||
$table->timestamp('keka_ms_token_expires_at')->nullable();
|
$table->timestamp('keka_ms_token_expires_at')->nullable();
|
||||||
@ -18,7 +20,7 @@ public function up(): void
|
|||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::table('users', function (Blueprint $table) {
|
Schema::table('users', function (Blueprint $table): void {
|
||||||
$table->dropColumn([
|
$table->dropColumn([
|
||||||
'keka_ms_access_token',
|
'keka_ms_access_token',
|
||||||
'keka_ms_refresh_token',
|
'keka_ms_refresh_token',
|
||||||
|
|||||||
@ -1,21 +1,23 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
use Illuminate\Database\Migrations\Migration;
|
use Illuminate\Database\Migrations\Migration;
|
||||||
use Illuminate\Database\Schema\Blueprint;
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
use Illuminate\Support\Facades\Schema;
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
return new class extends Migration
|
return new class() extends Migration
|
||||||
{
|
{
|
||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::table('users', function (Blueprint $table) {
|
Schema::table('users', function (Blueprint $table): void {
|
||||||
$table->boolean('keka_connected')->default(false);
|
$table->boolean('keka_connected')->default(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function down(): void
|
public function down(): void
|
||||||
{
|
{
|
||||||
Schema::table('users', function (Blueprint $table) {
|
Schema::table('users', function (Blueprint $table): void {
|
||||||
$table->dropColumn('keka_connected');
|
$table->dropColumn('keka_connected');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class() extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('app_roles', function (Blueprint $table): void {
|
||||||
|
$table->boolean('is_unlimited')->default(false)->comment('Is role unlimited?');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('app_roles', function (Blueprint $table): void {
|
||||||
|
$table->dropColumn('is_unlimited');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use App\Concerns\Confirmation;
|
use App\Concerns\Confirmation;
|
||||||
use App\Concerns\HandlesOperations;
|
use App\Concerns\HandlesOperations;
|
||||||
|
use App\Livewire\Forms\AssignAppToRoleForm;
|
||||||
use App\Livewire\Forms\CreateRoleForm;
|
use App\Livewire\Forms\CreateRoleForm;
|
||||||
use App\Services\AppRoleService;
|
use App\Services\AppRoleService;
|
||||||
use App\Data\Role\{AssignAppsToRoleData, CreateRoleData, RoleAppData, RoleUserData};
|
use App\Data\Role\{AssignAppsToRoleData, CreateRoleData, RoleAppData, RoleUserData};
|
||||||
@ -21,6 +22,7 @@ class extends Component {
|
|||||||
protected AppRoleService $appRoleService;
|
protected AppRoleService $appRoleService;
|
||||||
public bool $showCreateModal = false;
|
public bool $showCreateModal = false;
|
||||||
public CreateRoleForm $form;
|
public CreateRoleForm $form;
|
||||||
|
public AssignAppToRoleForm $assignForm;
|
||||||
protected RoleService $roleService;
|
protected RoleService $roleService;
|
||||||
public array $appsSearchable = [];
|
public array $appsSearchable = [];
|
||||||
|
|
||||||
@ -88,7 +90,8 @@ public function save(): void
|
|||||||
$this->form->validate();
|
$this->form->validate();
|
||||||
|
|
||||||
if ((int) $this->form->priority <= $userPriority) {
|
if ((int) $this->form->priority <= $userPriority) {
|
||||||
$this->addError('form.priority', 'The priority must be greater than your own highest role priority (' . $userPriority . ').');
|
$this->addError('form.priority',
|
||||||
|
'The priority must be greater than your own highest role priority ('.$userPriority.').');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +105,7 @@ public function save(): void
|
|||||||
roleId: $role->id,
|
roleId: $role->id,
|
||||||
validUpto: $this->form->isUnlimited ? now()->addYears(15) : $this->form->validUpto,
|
validUpto: $this->form->isUnlimited ? now()->addYears(15) : $this->form->validUpto,
|
||||||
appIds: $this->form->appIds,
|
appIds: $this->form->appIds,
|
||||||
|
isUnlimited: $this->assignForm->isUnlimited
|
||||||
));
|
));
|
||||||
$this->showCreateModal = false;
|
$this->showCreateModal = false;
|
||||||
$this->form->reset();
|
$this->form->reset();
|
||||||
|
|||||||
@ -132,7 +132,7 @@ public function openEditAppModal(int $assignmentId): void
|
|||||||
if ($assignment) {
|
if ($assignment) {
|
||||||
$this->assignForm->appIds = [$assignment['appId'] ?? $assignment['id']];
|
$this->assignForm->appIds = [$assignment['appId'] ?? $assignment['id']];
|
||||||
$this->assignForm->validUpto = $assignment['duration'];
|
$this->assignForm->validUpto = $assignment['duration'];
|
||||||
|
$this->assignForm->isUnlimited = $assignment['isUnlimited'];
|
||||||
$this->editingAppName = $assignment['appName'];
|
$this->editingAppName = $assignment['appName'];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,6 +178,7 @@ public function assignApp(): void
|
|||||||
roleId: $this->roleId,
|
roleId: $this->roleId,
|
||||||
validUpto: $this->assignForm->isUnlimited ? now()->addYears(15) : $this->assignForm->validUpto,
|
validUpto: $this->assignForm->isUnlimited ? now()->addYears(15) : $this->assignForm->validUpto,
|
||||||
appIds: $this->assignForm->appIds,
|
appIds: $this->assignForm->appIds,
|
||||||
|
isUnlimited: $this->assignForm->isUnlimited
|
||||||
));
|
));
|
||||||
$this->assignForm->reset();
|
$this->assignForm->reset();
|
||||||
$this->showAddAppModal = false;
|
$this->showAddAppModal = false;
|
||||||
@ -198,6 +199,7 @@ public function updateApp(): void
|
|||||||
roleId: $this->roleId,
|
roleId: $this->roleId,
|
||||||
validUpto: $this->assignForm->isUnlimited ? now()->addYears(15) : $this->assignForm->validUpto,
|
validUpto: $this->assignForm->isUnlimited ? now()->addYears(15) : $this->assignForm->validUpto,
|
||||||
appIds: $this->assignForm->appIds,
|
appIds: $this->assignForm->appIds,
|
||||||
|
isUnlimited: $this->assignForm->isUnlimited
|
||||||
));
|
));
|
||||||
|
|
||||||
$this->assignForm->reset();
|
$this->assignForm->reset();
|
||||||
@ -351,7 +353,7 @@ class="btn-sm">
|
|||||||
@endscope
|
@endscope
|
||||||
|
|
||||||
@scope('cell_duration', $row)
|
@scope('cell_duration', $row)
|
||||||
{{ Carbon::parse($row['duration'])->diffForHumans() }}
|
{{ $row['isUnlimited'] ? "Unlimited" : Carbon::parse($row['duration'])->diffForHumans() }}
|
||||||
@endscope
|
@endscope
|
||||||
|
|
||||||
@scope('actions', $row)
|
@scope('actions', $row)
|
||||||
|
|||||||
@ -165,17 +165,6 @@ class="w-3.5 h-3.5 inline animate-pulse text-amber-600 dark:text-amber-400"/>
|
|||||||
class="flex flex-col gap-3">
|
class="flex flex-col gap-3">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-xs font-semibold text-gray-700">Microsoft Integration</span>
|
<span class="text-xs font-semibold text-gray-700">Microsoft Integration</span>
|
||||||
@if($this->isEntraConnected)
|
|
||||||
<x-mary-badge
|
|
||||||
class="badge-soft badge-success text-[10px] font-semibold px-2 py-0.5">
|
|
||||||
Connected
|
|
||||||
</x-mary-badge>
|
|
||||||
@else
|
|
||||||
<x-mary-badge
|
|
||||||
class="badge-soft badge-neutral text-[10px] font-semibold px-2 py-0.5">
|
|
||||||
Not Connected
|
|
||||||
</x-mary-badge>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@ -218,17 +207,6 @@ class="btn-primary flex-1"
|
|||||||
class="flex flex-col gap-3">
|
class="flex flex-col gap-3">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<span class="text-xs font-semibold text-gray-700">Keka Integration</span>
|
<span class="text-xs font-semibold text-gray-700">Keka Integration</span>
|
||||||
@if($this->isKekaConnected)
|
|
||||||
<x-mary-badge
|
|
||||||
class="badge-soft badge-success text-[10px] font-semibold px-2 py-0.5">
|
|
||||||
Connected
|
|
||||||
</x-mary-badge>
|
|
||||||
@else
|
|
||||||
<x-mary-badge
|
|
||||||
class="badge-soft badge-neutral text-[10px] font-semibold px-2 py-0.5">
|
|
||||||
Not Connected
|
|
||||||
</x-mary-badge>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
@ -287,49 +265,6 @@ class="btn-sm btn-circle btn-soft btn-warning"
|
|||||||
/>
|
/>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if($service->provider_slug === 'azure-fd')
|
|
||||||
@if($this->isEntraConnected)
|
|
||||||
<x-mary-button
|
|
||||||
external
|
|
||||||
icon="lucide.external-link"
|
|
||||||
:link="route('entra.open')"
|
|
||||||
class="btn-sm btn-circle btn-soft btn-primary"
|
|
||||||
tooltip="Launch Service"
|
|
||||||
/>
|
|
||||||
@else
|
|
||||||
<x-mary-button
|
|
||||||
no-wire-navigate
|
|
||||||
icon="lucide.external-link"
|
|
||||||
:link="route('entra.connect')"
|
|
||||||
class="btn-sm btn-circle btn-soft btn-primary"
|
|
||||||
tooltip="Launch Service"
|
|
||||||
/>
|
|
||||||
@endif
|
|
||||||
@elseif($service->provider_slug === 'keka-hr')
|
|
||||||
@if($this->isKekaConnected)
|
|
||||||
<x-mary-button
|
|
||||||
external
|
|
||||||
icon="lucide.external-link"
|
|
||||||
:link="route('keka.connect')"
|
|
||||||
class="btn-sm btn-circle btn-soft btn-primary"
|
|
||||||
tooltip="Launch Service"
|
|
||||||
/>
|
|
||||||
@else
|
|
||||||
<x-mary-button
|
|
||||||
no-wire-navigate
|
|
||||||
icon="lucide.external-link"
|
|
||||||
:link="route('keka.connect')"
|
|
||||||
class="btn-sm btn-circle btn-soft btn-primary"
|
|
||||||
tooltip="Launch Service"
|
|
||||||
/>
|
|
||||||
@endif
|
|
||||||
@else
|
|
||||||
<x-mary-button
|
|
||||||
icon="lucide.external-link"
|
|
||||||
class="btn-sm btn-circle btn-soft btn-primary"
|
|
||||||
tooltip="Launch Service"
|
|
||||||
/>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -9,10 +9,10 @@
|
|||||||
RoleAndAppsPermissionEnum,
|
RoleAndAppsPermissionEnum,
|
||||||
UserPermissionEnum};
|
UserPermissionEnum};
|
||||||
use App\Http\Controllers\{EntraController, KekaController};
|
use App\Http\Controllers\{EntraController, KekaController};
|
||||||
|
use App\Http\Controllers\KekaOutlookController;
|
||||||
use App\Http\Controllers\{ResolveDashboardRouteController, SamlIdpController};
|
use App\Http\Controllers\{ResolveDashboardRouteController, SamlIdpController};
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
use Spatie\Permission\Middleware\{PermissionMiddleware, RoleMiddleware};
|
use Spatie\Permission\Middleware\{PermissionMiddleware, RoleMiddleware};
|
||||||
use App\Http\Controllers\KekaOutlookController;
|
|
||||||
|
|
||||||
Route::redirect('/', '/dashboard')->name('home');
|
Route::redirect('/', '/dashboard')->name('home');
|
||||||
|
|
||||||
@ -96,7 +96,6 @@
|
|||||||
// Disconnect / revoke saved token
|
// Disconnect / revoke saved token
|
||||||
Route::delete('disconnect', [EntraController::class, 'disconnect'])->name('disconnect');
|
Route::delete('disconnect', [EntraController::class, 'disconnect'])->name('disconnect');
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::middleware(['auth', 'verified'])->prefix('keka')->name('keka.')->group(function (): void {
|
Route::middleware(['auth', 'verified'])->prefix('keka')->name('keka.')->group(function (): void {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user