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 string $validUpto,
|
||||
/** @var int[] */
|
||||
public array $appIds
|
||||
public array $appIds,
|
||||
public bool $isUnlimited = false,
|
||||
) {}
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ public function __construct(
|
||||
public int $appId,
|
||||
public string $appName,
|
||||
public string $duration,
|
||||
public bool $isUnlimited = false,
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
||||
@ -5,8 +5,8 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Services\KekaOutlookService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\{RedirectResponse, Request};
|
||||
use RuntimeException;
|
||||
|
||||
final class KekaOutlookController extends Controller
|
||||
{
|
||||
@ -31,7 +31,7 @@ public function connect(Request $request): RedirectResponse
|
||||
if ($this->kekaOutlook->isConnected($user)) {
|
||||
try {
|
||||
$this->kekaOutlook->getValidToken($user);
|
||||
} catch (\RuntimeException) {
|
||||
} catch (RuntimeException) {
|
||||
// Token fully expired/revoked → re-authenticate
|
||||
return redirect()->away($this->kekaOutlook->getAuthUrl());
|
||||
}
|
||||
@ -54,7 +54,7 @@ public function callback(Request $request): RedirectResponse
|
||||
{
|
||||
if ($request->filled('error')) {
|
||||
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'))) {
|
||||
@ -73,7 +73,7 @@ public function callback(Request $request): RedirectResponse
|
||||
|
||||
if (! $result['success']) {
|
||||
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
|
||||
|
||||
@ -14,7 +14,7 @@
|
||||
* @property int $role_id
|
||||
* @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
|
||||
{
|
||||
public $timestamps = false;
|
||||
@ -36,4 +36,11 @@ public function role(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Role::class, 'role_id');
|
||||
}
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'is_unlimited' => 'boolean',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,17 +5,23 @@
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Carbon\CarbonImmutable;
|
||||
use Database\Factories\UserFactory;
|
||||
use DateTime;
|
||||
use Illuminate\Database\Eloquent\Attributes\{Fillable, Hidden};
|
||||
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\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
use Spatie\Permission\Models\Permission;
|
||||
use Spatie\Permission\Traits\HasRoles;
|
||||
|
||||
/**
|
||||
* @property DateTime|CarbonImmutable|null $keka_ms_token_expires_at
|
||||
*/
|
||||
#[Fillable(['name', 'email', 'password', 'immutable_id'])]
|
||||
#[Hidden(['password', 'two_factor_secret', 'two_factor_recovery_codes', 'remember_token'])]
|
||||
final class User extends Authenticatable
|
||||
@ -27,10 +33,10 @@ final class User extends Authenticatable
|
||||
use Notifiable;
|
||||
use TwoFactorAuthenticatable;
|
||||
|
||||
public function restrictedPermissions(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
public function restrictedPermissions(): BelongsToMany
|
||||
{
|
||||
return $this->belongsToMany(
|
||||
\Spatie\Permission\Models\Permission::class,
|
||||
Permission::class,
|
||||
'restricted_user_permissions',
|
||||
'user_id',
|
||||
'permission_id'
|
||||
@ -50,17 +56,11 @@ public function initials(): string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
* Get the OAuth token for a specific provider.
|
||||
*/
|
||||
protected function casts(): array
|
||||
public function oauthToken(string $provider): ?OauthToken
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'keka_ms_token_expires_at' => 'datetime',
|
||||
];
|
||||
return $this->oauthTokens()->forProvider($provider)->first();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,10 +72,16 @@ public function oauthTokens(): HasMany
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the OAuth token for a specific provider.
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function oauthToken(string $provider): ?OauthToken
|
||||
protected function casts(): array
|
||||
{
|
||||
return $this->oauthTokens()->forProvider($provider)->first();
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
'keka_ms_token_expires_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,12 +39,13 @@ public function assign(AssignAppsToRoleData $data): void
|
||||
'role_id' => $data->roleId,
|
||||
'app_id' => $appId,
|
||||
'duration' => $data->validUpto,
|
||||
'is_unlimited' => $data->isUnlimited,
|
||||
], $data->appIds);
|
||||
|
||||
AppRole::query()->upsert(
|
||||
$records,
|
||||
['role_id', 'app_id'],
|
||||
['duration']
|
||||
['duration', 'is_unlimited'],
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -123,7 +124,7 @@ public function getAppsForRole(int $roleId): DataCollection
|
||||
->with('app:id,name')
|
||||
->where('role_id', $roleId)
|
||||
->orderBy('id')
|
||||
->get(['id', 'app_id', 'role_id', 'duration']);
|
||||
->get(['id', 'app_id', 'role_id', 'duration', 'is_unlimited']);
|
||||
|
||||
return RoleAppData::collect(
|
||||
$assignments->map(fn (AppRole $row): array => [
|
||||
@ -131,6 +132,7 @@ public function getAppsForRole(int $roleId): DataCollection
|
||||
'appId' => $row->app_id,
|
||||
'appName' => $row->app->name ?? '',
|
||||
'duration' => $row->duration,
|
||||
'isUnlimited' => $row->is_unlimited,
|
||||
])->all(),
|
||||
DataCollection::class,
|
||||
);
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Str;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Handles Microsoft OAuth (delegated, authorization code flow) for Keka-Outlook integration.
|
||||
@ -31,11 +32,11 @@ final class KekaOutlookService
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->clientId = (string) config('microsoft.client_id');
|
||||
$this->clientId = (string) config('microsoft.client_id');
|
||||
$this->clientSecret = (string) config('microsoft.client_secret');
|
||||
$this->tenantId = (string) config('microsoft.tenant_id');
|
||||
$this->redirectUri = (string) config('microsoft.keka_redirect_uri');
|
||||
$this->scopes = config('microsoft.keka_scopes', [
|
||||
$this->tenantId = (string) config('microsoft.tenant_id');
|
||||
$this->redirectUri = (string) config('microsoft.keka_redirect_uri');
|
||||
$this->scopes = config('microsoft.keka_scopes', [
|
||||
'openid',
|
||||
'profile',
|
||||
'email',
|
||||
@ -57,14 +58,14 @@ public function getAuthUrl(): string
|
||||
session(['keka_ms_oauth_state' => $state]);
|
||||
|
||||
$query = http_build_query([
|
||||
'client_id' => $this->clientId,
|
||||
'client_id' => $this->clientId,
|
||||
'response_type' => 'code',
|
||||
'redirect_uri' => $this->redirectUri,
|
||||
'redirect_uri' => $this->redirectUri,
|
||||
'response_mode' => 'query',
|
||||
'scope' => implode(' ', $this->scopes),
|
||||
'state' => $state,
|
||||
'scope' => implode(' ', $this->scopes),
|
||||
'state' => $state,
|
||||
// prompt=select_account forces account chooser on first connect.
|
||||
'prompt' => 'select_account',
|
||||
'prompt' => 'select_account',
|
||||
]);
|
||||
|
||||
return "https://login.microsoftonline.com/{$this->tenantId}/oauth2/v2.0/authorize?{$query}";
|
||||
@ -94,12 +95,12 @@ public function handleCallback(User $user, string $code): array
|
||||
$response = Http::asForm()->post(
|
||||
"https://login.microsoftonline.com/{$this->tenantId}/oauth2/v2.0/token",
|
||||
[
|
||||
'client_id' => $this->clientId,
|
||||
'client_id' => $this->clientId,
|
||||
'client_secret' => $this->clientSecret,
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $code,
|
||||
'redirect_uri' => $this->redirectUri,
|
||||
'scope' => implode(' ', $this->scopes),
|
||||
'grant_type' => 'authorization_code',
|
||||
'code' => $code,
|
||||
'redirect_uri' => $this->redirectUri,
|
||||
'scope' => implode(' ', $this->scopes),
|
||||
]
|
||||
);
|
||||
|
||||
@ -133,7 +134,7 @@ public function handleCallback(User $user, string $code): array
|
||||
*/
|
||||
private function storeTokens(User $user, array $tokenData): void
|
||||
{
|
||||
$user->keka_ms_access_token = $tokenData['access_token'];
|
||||
$user->keka_ms_access_token = $tokenData['access_token'];
|
||||
$user->keka_ms_token_expires_at = now()->addSeconds((int) $tokenData['expires_in']);
|
||||
|
||||
// Microsoft only returns refresh_token if 'offline_access' scope was granted.
|
||||
@ -145,24 +146,16 @@ private function storeTokens(User $user, array $tokenData): void
|
||||
$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.
|
||||
* 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
|
||||
{
|
||||
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)
|
||||
@ -177,21 +170,29 @@ public function getValidToken(User $user): string
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
{
|
||||
$response = Http::asForm()->post(
|
||||
"https://login.microsoftonline.com/{$this->tenantId}/oauth2/v2.0/token",
|
||||
[
|
||||
'client_id' => $this->clientId,
|
||||
'client_id' => $this->clientId,
|
||||
'client_secret' => $this->clientSecret,
|
||||
'grant_type' => 'refresh_token',
|
||||
'grant_type' => 'refresh_token',
|
||||
'refresh_token' => $user->keka_ms_refresh_token,
|
||||
'scope' => implode(' ', $this->scopes),
|
||||
'scope' => implode(' ', $this->scopes),
|
||||
]
|
||||
);
|
||||
|
||||
@ -199,7 +200,7 @@ private function refreshToken(User $user): string
|
||||
// Refresh token expired/revoked -> force user to reconnect.
|
||||
$this->disconnect($user);
|
||||
|
||||
throw new \RuntimeException(
|
||||
throw new RuntimeException(
|
||||
$response->json('error_description', 'Session expired. Please reconnect Keka.')
|
||||
);
|
||||
}
|
||||
@ -215,10 +216,10 @@ private function refreshToken(User $user): string
|
||||
*/
|
||||
public function disconnect(User $user): void
|
||||
{
|
||||
$user->keka_ms_access_token = null;
|
||||
$user->keka_ms_refresh_token = null;
|
||||
$user->keka_ms_access_token = null;
|
||||
$user->keka_ms_refresh_token = null;
|
||||
$user->keka_ms_token_expires_at = null;
|
||||
$user->keka_ms_email = null;
|
||||
$user->keka_ms_email = null;
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
@ -125,7 +125,7 @@
|
||||
'store' => env('APP_MAINTENANCE_STORE', 'database'),
|
||||
],
|
||||
|
||||
// Redirect URI registered in Azure: http://localhost:8000/keka/callback
|
||||
// Redirect URI registered in Azure: http://localhost:8000/keka/callback
|
||||
'keka_redirect_uri' => env('KEKA_MS_REDIRECT_URI', 'http://localhost:8000/keka/callback'),
|
||||
|
||||
// Where to send the user after a successful (or silent) connection.
|
||||
|
||||
@ -1,17 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
'client_id' => env('MICROSOFT_GRAPH_CLIENT_ID'),
|
||||
'client_id' => env('MICROSOFT_GRAPH_CLIENT_ID'),
|
||||
'client_secret' => env('MICROSOFT_GRAPH_CLIENT_SECRET'),
|
||||
'tenant_id' => env('MICROSOFT_GRAPH_TENANT_ID'),
|
||||
'tenant_id' => env('MICROSOFT_GRAPH_TENANT_ID'),
|
||||
|
||||
// Your app's redirect URI for Keka OAuth flow
|
||||
'keka_redirect_uri' => env('KEKA_MS_REDIRECT_URI', 'http://localhost:8000/keka/callback'),
|
||||
|
||||
// After first-time login: Keka's own Microsoft SSO URL
|
||||
// After already connected: Keka dashboard
|
||||
'keka_login_url' => env('KEKA_LOGIN_URL', 'https://sentientgeeks.keka.com/'),
|
||||
'keka_dashboard_url'=> env('KEKA_DASHBOARD_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_scopes' => [
|
||||
'openid',
|
||||
|
||||
@ -1,14 +1,16 @@
|
||||
<?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
|
||||
return new class() extends Migration
|
||||
{
|
||||
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_refresh_token')->nullable();
|
||||
$table->timestamp('keka_ms_token_expires_at')->nullable();
|
||||
@ -18,7 +20,7 @@ public function up(): void
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
Schema::table('users', function (Blueprint $table): void {
|
||||
$table->dropColumn([
|
||||
'keka_ms_access_token',
|
||||
'keka_ms_refresh_token',
|
||||
|
||||
@ -1,21 +1,23 @@
|
||||
<?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
|
||||
return new class() extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
Schema::table('users', function (Blueprint $table): void {
|
||||
$table->boolean('keka_connected')->default(false);
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
Schema::table('users', function (Blueprint $table): void {
|
||||
$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\HandlesOperations;
|
||||
use App\Livewire\Forms\AssignAppToRoleForm;
|
||||
use App\Livewire\Forms\CreateRoleForm;
|
||||
use App\Services\AppRoleService;
|
||||
use App\Data\Role\{AssignAppsToRoleData, CreateRoleData, RoleAppData, RoleUserData};
|
||||
@ -21,6 +22,7 @@ class extends Component {
|
||||
protected AppRoleService $appRoleService;
|
||||
public bool $showCreateModal = false;
|
||||
public CreateRoleForm $form;
|
||||
public AssignAppToRoleForm $assignForm;
|
||||
protected RoleService $roleService;
|
||||
public array $appsSearchable = [];
|
||||
|
||||
@ -88,7 +90,8 @@ public function save(): void
|
||||
$this->form->validate();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -102,6 +105,7 @@ public function save(): void
|
||||
roleId: $role->id,
|
||||
validUpto: $this->form->isUnlimited ? now()->addYears(15) : $this->form->validUpto,
|
||||
appIds: $this->form->appIds,
|
||||
isUnlimited: $this->assignForm->isUnlimited
|
||||
));
|
||||
$this->showCreateModal = false;
|
||||
$this->form->reset();
|
||||
|
||||
@ -132,7 +132,7 @@ public function openEditAppModal(int $assignmentId): void
|
||||
if ($assignment) {
|
||||
$this->assignForm->appIds = [$assignment['appId'] ?? $assignment['id']];
|
||||
$this->assignForm->validUpto = $assignment['duration'];
|
||||
|
||||
$this->assignForm->isUnlimited = $assignment['isUnlimited'];
|
||||
$this->editingAppName = $assignment['appName'];
|
||||
}
|
||||
|
||||
@ -178,6 +178,7 @@ public function assignApp(): void
|
||||
roleId: $this->roleId,
|
||||
validUpto: $this->assignForm->isUnlimited ? now()->addYears(15) : $this->assignForm->validUpto,
|
||||
appIds: $this->assignForm->appIds,
|
||||
isUnlimited: $this->assignForm->isUnlimited
|
||||
));
|
||||
$this->assignForm->reset();
|
||||
$this->showAddAppModal = false;
|
||||
@ -198,6 +199,7 @@ public function updateApp(): void
|
||||
roleId: $this->roleId,
|
||||
validUpto: $this->assignForm->isUnlimited ? now()->addYears(15) : $this->assignForm->validUpto,
|
||||
appIds: $this->assignForm->appIds,
|
||||
isUnlimited: $this->assignForm->isUnlimited
|
||||
));
|
||||
|
||||
$this->assignForm->reset();
|
||||
@ -351,7 +353,7 @@ class="btn-sm">
|
||||
@endscope
|
||||
|
||||
@scope('cell_duration', $row)
|
||||
{{ Carbon::parse($row['duration'])->diffForHumans() }}
|
||||
{{ $row['isUnlimited'] ? "Unlimited" : Carbon::parse($row['duration'])->diffForHumans() }}
|
||||
@endscope
|
||||
|
||||
@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">
|
||||
<div class="flex items-center justify-between">
|
||||
<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 class="flex items-center gap-2">
|
||||
@ -218,17 +207,6 @@ class="btn-primary flex-1"
|
||||
class="flex flex-col gap-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<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 class="flex items-center gap-2">
|
||||
@ -287,49 +265,6 @@ class="btn-sm btn-circle btn-soft btn-warning"
|
||||
/>
|
||||
@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>
|
||||
|
||||
@ -9,10 +9,10 @@
|
||||
RoleAndAppsPermissionEnum,
|
||||
UserPermissionEnum};
|
||||
use App\Http\Controllers\{EntraController, KekaController};
|
||||
use App\Http\Controllers\KekaOutlookController;
|
||||
use App\Http\Controllers\{ResolveDashboardRouteController, SamlIdpController};
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Spatie\Permission\Middleware\{PermissionMiddleware, RoleMiddleware};
|
||||
use App\Http\Controllers\KekaOutlookController;
|
||||
|
||||
Route::redirect('/', '/dashboard')->name('home');
|
||||
|
||||
@ -96,7 +96,6 @@
|
||||
// Disconnect / revoke saved token
|
||||
Route::delete('disconnect', [EntraController::class, 'disconnect'])->name('disconnect');
|
||||
|
||||
|
||||
});
|
||||
|
||||
Route::middleware(['auth', 'verified'])->prefix('keka')->name('keka.')->group(function (): void {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user