From 8e9cfa4bf92de35c09168044e9f971b774e84182 Mon Sep 17 00:00:00 2001 From: nemerald-voip Date: Mon, 16 Dec 2024 00:13:20 -0800 Subject: [PATCH 01/19] redesigned apps page --- app/Console/Commands/UpdateApp.php | 2 + app/Console/Commands/Updates/Update0916.php | 51 ++ app/Http/Controllers/AppsController.php | 355 ++++++++---- app/Models/DefaultSettings.php | 18 +- app/Models/Domain.php | 1 + app/helpers.php | 2 +- config/version.php | 2 +- resources/js/Pages/RingotelAppSettings.vue | 565 ++++++++++++++++++++ resources/views/layouts/apps/list.blade.php | 20 +- routes/web.php | 3 +- 10 files changed, 892 insertions(+), 127 deletions(-) create mode 100644 app/Console/Commands/Updates/Update0916.php create mode 100644 resources/js/Pages/RingotelAppSettings.vue diff --git a/app/Console/Commands/UpdateApp.php b/app/Console/Commands/UpdateApp.php index 6a5d9005..074c6b2a 100644 --- a/app/Console/Commands/UpdateApp.php +++ b/app/Console/Commands/UpdateApp.php @@ -6,6 +6,7 @@ use App\Services\GitHubApiService; use Symfony\Component\Process\Process; use App\Console\Commands\Updates\Update097; +use App\Console\Commands\Updates\Update0916; class UpdateApp extends Command @@ -48,6 +49,7 @@ public function handle() $updateSteps = [ '0.9.7' => Update097::class, '0.9.11' => Update097::class, + '0.9.16' => Update0916::class, // Add more versions as needed ]; diff --git a/app/Console/Commands/Updates/Update0916.php b/app/Console/Commands/Updates/Update0916.php new file mode 100644 index 00000000..c1ece79c --- /dev/null +++ b/app/Console/Commands/Updates/Update0916.php @@ -0,0 +1,51 @@ +where('default_setting_subcategory', $subcategory) + ->first(); + + if ($defaultSetting) { + // Update the description if the record exists + $defaultSetting->default_setting_description = $newDescription; + $defaultSetting->save(); + echo "Updated description for existing record in category '$category' and subcategory '$subcategory'.\n"; + } else { + // Create a new record if it does not exist + DefaultSettings::create([ + 'default_setting_uuid' => \Illuminate\Support\Str::uuid()->toString(), // Generate a new UUID + 'default_setting_category' => $category, + 'default_setting_subcategory' => $subcategory, + 'default_setting_name' => 'text', + 'default_setting_value' => '', + 'default_setting_enabled' => 't', + 'default_setting_description' => $newDescription, + ]); + echo "Created new record in category '$category' and subcategory '$subcategory'.\n"; + } + + return true; + } catch (\Exception $e) { + echo "Error applying update 0.9.16: " . $e->getMessage() . "\n"; + return false; + } + } +} diff --git a/app/Http/Controllers/AppsController.php b/app/Http/Controllers/AppsController.php index f66fa197..7a597f35 100644 --- a/app/Http/Controllers/AppsController.php +++ b/app/Http/Controllers/AppsController.php @@ -2,24 +2,37 @@ namespace App\Http\Controllers; +use Inertia\Inertia; use App\Models\Domain; use App\Models\Extensions; -use App\Models\MobileAppPasswordResetLinks; +use Illuminate\Support\Str; use Illuminate\Http\Request; use App\Models\DomainSettings; use App\Models\MobileAppUsers; use App\Jobs\SendAppCredentials; -use App\Services\RingotelApiService; use Illuminate\Support\Facades\Log; +use App\Services\RingotelApiService; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Session; -use Illuminate\Support\Str; +use App\Models\MobileAppPasswordResetLinks; use SimpleSoftwareIO\QrCode\Facades\QrCode; class AppsController extends Controller { protected $ringotelApiService; + public $model; + public $filters = []; + public $sortField; + public $sortOrder; + protected $viewName = 'RingotelAppSettings'; + protected $searchable = ['domain_name', 'domain_description']; + + public function __construct() + { + $this->model = new Domain(); + } + /** * Display a listing of the resource. * @@ -27,28 +40,127 @@ class AppsController extends Controller */ public function index() { - // Check all domains for registration in Ringotel Shell - $domains = Domain::where('domain_enabled','true') - ->orderBy('domain_description') - ->get(); + return Inertia::render( + $this->viewName, + [ + 'data' => function () { + return $this->getData(); + }, + + 'routes' => [ + 'current_page' => route('apps.index'), + 'store' => route('apps.store'), + 'item_options' => route('apps.item.options'), + ] + ] + ); + + } - foreach($domains as $domain) { - $settings = $domain->settings() - ->where('domain_setting_category', 'app shell') - ->where('domain_setting_subcategory', 'org_id') - ->get(); + /** + * Get data + */ + public function getData($paginate = 50) + { - if ($settings->count() > 0) { - $domain->setAttribute('status', 'true'); - } else { - $domain->setAttribute('status', 'false'); - } + // Check if search parameter is present and not empty + if (!empty(request('filterData.search'))) { + $this->filters['search'] = request('filterData.search'); + } + + // Add sorting criteria + $this->sortField = request()->get('sortField', 'domain_name'); // Default to 'voicemail_id' + $this->sortOrder = request()->get('sortOrder', 'asc'); // Default to descending + $data = $this->builder($this->filters); + + // Apply pagination if requested + if ($paginate) { + $data = $data->paginate($paginate); + } else { + $data = $data->get(); // This will return a collection + } + + // Add `ringotel_status` dynamically + $data->each(function ($domain) { + $domain->ringotel_status = $domain->settings() + ->where('domain_setting_category', 'app shell') + ->where('domain_setting_subcategory', 'org_id') + ->where('domain_setting_enabled', true) + ->exists() ? 'true' : 'false'; + }); + + return $data; + } + + /** + * @param array $filters + * @return Builder + */ + public function builder(array $filters = []) + { + $data = $this->model::query(); + // Get all domains with 'domain_enabled' set to 'true' and eager load settings + $data->where('domain_enabled', 'true') + ->with(['settings' => function ($query) { + $query->select('domain_uuid', 'domain_setting_uuid', 'domain_setting_category', 'domain_setting_subcategory', 'domain_setting_value') + ->where('domain_setting_category', 'app shell') + ->where('domain_setting_subcategory', 'org_id') + ->where('domain_setting_enabled', true); + }]); + + // Add 'status' attribute based on the existence of settings + // foreach ($domains as $domain) { + // $domain->status = $domain->settings->isNotEmpty() ? 'true' : 'false'; + // } + + + $data->select( + 'domain_uuid', + 'domain_name', + 'domain_description', + ); + + if (is_array($filters)) { + foreach ($filters as $field => $value) { + if (method_exists($this, $method = "filter" . ucfirst($field))) { + $this->$method($data, $value); + } + } } - return view('layouts.apps.list') - ->with("domains",$domains); + // Apply sorting + $data->orderBy($this->sortField, $this->sortOrder); + + return $data; + } + + /** + * @param $query + * @param $value + * @return void + */ + protected function filterSearch($query, $value) + { + $searchable = $this->searchable; + + // Case-insensitive partial string search in the specified fields + $query->where(function ($query) use ($value, $searchable) { + foreach ($searchable as $field) { + if (strpos($field, '.') !== false) { + // Nested field (e.g., 'extension.name_formatted') + [$relation, $nestedField] = explode('.', $field, 2); + + $query->orWhereHas($relation, function ($query) use ($nestedField, $value) { + $query->where($nestedField, 'ilike', '%' . $value . '%'); + }); + } else { + // Direct field + $query->orWhere($field, 'ilike', '%' . $value . '%'); + } + } + }); } /** @@ -56,11 +168,30 @@ public function index() * * @return \Illuminate\Http\JsonResponse */ - public function createOrganization(Request $request) + public function createOrganization(RingotelApiService $ringotelApiService) { - $request->dont_send_user_credentials = isset($request->dont_send_user_credentials) - ? $request->dont_send_user_credentials == 'true' ? 'true' : 'false' - : get_domain_setting('dont_send_user_credentials') ?? 'false'; + $this->ringotelApiService = $ringotelApiService; + try { + $users = $this->ringotelApiService->getUsersByOrgId($orgId); + } catch (\Exception $e) { + logger($e->getMessage()); + return response()->json([ + 'error' => [ + 'message' => $e->getMessage(), + ], + ], 400); + } + + + return response()->json([ + 'users' => $users, + 'status' => 200, + 'success' => [ + 'message' => 'The request processed successfully' + ] + ]); + + $request->dont_send_user_credentials = $request->dont_send_user_credentials === 'true' ? 'true' : 'false'; $data = array( 'method' => 'createOrganization', @@ -77,25 +208,26 @@ public function createOrganization(Request $request) $response = Http::ringotel() //->dd() ->timeout(5) - ->withBody(json_encode($data),'application/json') + ->withBody(json_encode($data), 'application/json') ->post('/') ->throw(function ($response, $e) { return response()->json([ 'error' => 401, - 'message' => 'Unable to create organization']); - }) - ->json(); + 'message' => 'Unable to create organization' + ]); + }) + ->json(); //dd(isset($response['error'])); // If successful store Org ID and return success status - if (isset($response['result'])){ + if (isset($response['result'])) { // Add received OrgID to the request and store it in database $request->merge(['org_id' => $response['result']['id']]); - if (!appsStoreOrganizationDetails($request)){ + if (!appsStoreOrganizationDetails($request)) { return response()->json([ 'organization_name' => $request->organization_name, 'organization_domain' => $request->organization_domain, @@ -117,27 +249,29 @@ public function createOrganization(Request $request) 'organization_region' => $request->organization_region, 'org_id' => $response['result']['id'], 'protocol' => ($protocol) ? $protocol : "", - 'connection_port' => ($port) ? $port : config("ringotel.connection_port"), - 'outbound_proxy' => ($proxy) ? $proxy : config("ringotel.outbound_proxy"), + 'connection_port' => ($port) ? $port : "", + 'outbound_proxy' => ($proxy) ? $proxy : "", 'success' => [ 'message' => 'Organization created successfully', ] ]); - // Otherwise return failed status + // Otherwise return failed status } elseif (isset($response['error'])) { return response()->json([ 'error' => 401, 'organization_name' => $request->organization_name, 'organization_domain' => $request->organization_domain, 'organization_region' => $request->organization_region, - 'message' => $response['error']['message']]); + 'message' => $response['error']['message'] + ]); } else { return response()->json([ 'error' => 401, 'organization_name' => $request->organization_name, 'organization_domain' => $request->organization_domain, 'organization_region' => $request->organization_region, - 'message' => 'Unknown error']); + 'message' => 'Unknown error' + ]); } } @@ -191,10 +325,10 @@ public function destroyOrganization(Request $request, Domain $domain) ]); } //Detele records from database - $appOrgID = DomainSettings::where('domain_uuid',$domain->domain_uuid) - ->where ('domain_setting_category', 'app shell') - ->where ('domain_setting_subcategory', 'org_id') - ->first(); + $appOrgID = DomainSettings::where('domain_uuid', $domain->domain_uuid) + ->where('domain_setting_category', 'app shell') + ->where('domain_setting_subcategory', 'org_id') + ->first(); Log::info($appOrgID); @@ -215,12 +349,12 @@ public function destroyOrganization(Request $request, Domain $domain) // !!!!! TODO: The code below is unreachable, do we need it ? !!!!! // If successful store Org ID and return success status - if (isset($response['result'])){ + if (isset($response['result'])) { // Add recieved OrgID to the request and store it in database $request->merge(['org_id' => $response['result']['id']]); - if (!appsStoreOrganizationDetails($request)){ + if (!appsStoreOrganizationDetails($request)) { return response()->json([ 'organization_name' => $request->organization_name, 'organization_domain' => $request->organization_domain, @@ -237,21 +371,23 @@ public function destroyOrganization(Request $request, Domain $domain) 'org_id' => $response['result']['id'], 'message' => 'Organization created succesfully', ]); - // Otherwise return failed status + // Otherwise return failed status } elseif (isset($response['error'])) { return response()->json([ 'error' => 401, 'organization_name' => $request->organization_name, 'organization_domain' => $request->organization_domain, 'organization_region' => $request->organization_region, - 'message' => $response['error']['message']]); + 'message' => $response['error']['message'] + ]); } else { return response()->json([ 'error' => 401, 'organization_name' => $request->organization_name, 'organization_domain' => $request->organization_domain, 'organization_region' => $request->organization_region, - 'message' => 'Unknown error']); + 'message' => 'Unknown error' + ]); } } @@ -264,10 +400,7 @@ public function destroyOrganization(Request $request, Domain $domain) * * @return \Illuminate\Http\Response */ - public function updateOrganization(Request $request) - { - - } + public function updateOrganization(Request $request) {} @@ -276,7 +409,7 @@ public function updateOrganization(Request $request) * * @return \Illuminate\Http\JsonResponse */ - public function createConnection (Request $request) + public function createConnection(Request $request) { // Build data array $data = array( @@ -344,37 +477,37 @@ public function createConnection (Request $request) // Add codecs - if (isset($request->connection_codec_u711)){ + if (isset($request->connection_codec_u711)) { $codec = array( - 'codec' => 'G.711 Ulaw', - 'frame' => 20 - ); - $codecs[]=$codec; + 'codec' => 'G.711 Ulaw', + 'frame' => 20 + ); + $codecs[] = $codec; } - if (isset($request->connection_codec_a711)){ + if (isset($request->connection_codec_a711)) { $codec = array( - 'codec' => 'G.711 Alaw', - 'frame' => 20 - ); - $codecs[]=$codec; + 'codec' => 'G.711 Alaw', + 'frame' => 20 + ); + $codecs[] = $codec; } - if (isset($request->connection_codec_729)){ + if (isset($request->connection_codec_729)) { $codec = array( - 'codec' => 'G.729', - 'frame' => 20 - ); - $codecs[]=$codec; + 'codec' => 'G.729', + 'frame' => 20 + ); + $codecs[] = $codec; } - if (isset($request->connection_codec_opus)){ + if (isset($request->connection_codec_opus)) { $codec = array( - 'codec' => 'OPUS', - 'frame' => 20 - ); - $codecs[]=$codec; + 'codec' => 'OPUS', + 'frame' => 20 + ); + $codecs[] = $codec; } $data['params']['provision']['codecs'] = $codecs; @@ -383,22 +516,23 @@ public function createConnection (Request $request) $response = Http::ringotel() //->dd() ->timeout(5) - ->withBody(json_encode($data),'application/json') + ->withBody(json_encode($data), 'application/json') ->post('/') ->throw(function ($response, $e) { return response()->json([ 'error' => 401, - 'message' => 'Unable to create connection']); - }) - ->json(); + 'message' => 'Unable to create connection' + ]); + }) + ->json(); // If successful return success status - if (isset($response['result'])){ + if (isset($response['result'])) { // Add recieved OrgID to the request and store it in database $request->merge(['conn_id' => $response['result']['id']]); - if (!appsStoreConnectionDetails($request)){ + if (!appsStoreConnectionDetails($request)) { return response()->json([ 'connection_name' => $request->connection_name, 'connection_domain' => $request->connection_domain, @@ -415,7 +549,7 @@ public function createConnection (Request $request) 'conn_id' => $response['result']['id'], 'message' => 'Connection created succesfully', ]); - // Otherwise return failed status + // Otherwise return failed status } elseif (isset($response['error'])) { return response()->json([ 'error' => 401, @@ -423,7 +557,8 @@ public function createConnection (Request $request) 'connection_domain' => $request->connection_domain, 'org_id' => $request->org_id, 'conn_id' => $response['result']['id'], - 'message' => $response['error']['message']]); + 'message' => $response['error']['message'] + ]); } else { return response()->json([ 'error' => 401, @@ -431,7 +566,8 @@ public function createConnection (Request $request) 'connection_domain' => $request->connection_domain, 'org_id' => $request->org_id, 'conn_id' => $response['result']['id'], - 'message' => 'Unknown error']); + 'message' => 'Unknown error' + ]); } } @@ -441,10 +577,7 @@ public function createConnection (Request $request) * * @return \Illuminate\Http\Response */ - public function updateConnection(Request $request) - { - - } + public function updateConnection(Request $request) {} /** @@ -493,8 +626,6 @@ public function getUsersByOrgId(RingotelApiService $ringotelApiService, $orgId) $this->ringotelApiService = $ringotelApiService; try { $users = $this->ringotelApiService->getUsersByOrgId($orgId); - - logger($users); } catch (\Exception $e) { logger($e->getMessage()); return response()->json([ @@ -525,8 +656,8 @@ public function syncOrganizations(Request $request) $app_array = $request->get('app_array'); - foreach ($app_array as $id=>$domain_uuid) { - // Store new record + foreach ($app_array as $id => $domain_uuid) { + // Store new record $domainSettings = DomainSettings::create([ 'domain_uuid' => $domain_uuid, 'domain_setting_category' => 'app shell', @@ -537,7 +668,7 @@ public function syncOrganizations(Request $request) ]); $saved = $domainSettings->save(); - if (!$saved){ + if (!$saved) { return response()->json([ 'status' => 401, 'error' => [ @@ -553,7 +684,6 @@ public function syncOrganizations(Request $request) 'message' => 'All organizations were successfully synced' ] ]); - } /** @@ -584,11 +714,11 @@ public function syncUsers(Request $request, Domain $domain) } // If successful continue - if (isset($response['result'])){ + if (isset($response['result'])) { $connections = $response['result']; $app_domain = $response['result'][0]['domain']; - // Otherwise return failed status + // Otherwise return failed status } elseif (isset($response['error'])) { return response()->json([ 'status' => 401, @@ -605,15 +735,15 @@ public function syncUsers(Request $request, Domain $domain) ]); } - foreach ($connections as $connection){ + foreach ($connections as $connection) { //Get all users for this connection $response = appsGetUsers($org_id, $connection['id']); // If successful continue - if (isset($response['result'])){ + if (isset($response['result'])) { $users = $response['result']; - // Otherwise return failed status + // Otherwise return failed status } elseif (isset($response['error'])) { return response()->json([ 'status' => 401, @@ -630,10 +760,10 @@ public function syncUsers(Request $request, Domain $domain) ]); } - foreach ($users as $user){ + foreach ($users as $user) { // Get each user's extension $extension = Extensions::where('extension', $user['extension']) - ->where ('domain_uuid', $domain->domain_uuid) + ->where('domain_uuid', $domain->domain_uuid) ->first(); if ($extension) { // Save returned user info in database @@ -648,7 +778,6 @@ public function syncUsers(Request $request, Domain $domain) $appUser->save(); } } - } return response()->json([ @@ -657,7 +786,6 @@ public function syncUsers(Request $request, Domain $domain) 'message' => 'Apps have been synced successfully' ] ]); - } /** @@ -682,7 +810,7 @@ public function mobileAppUserSettings(Request $request, Extensions $extension) $org_id = appsGetOrganizationDetails($extension->domain_uuid); // If Organization isn't set up return - if(!isset($org_id)) { + if (!isset($org_id)) { return response()->json([ 'status' => 401, 'error' => [ @@ -695,11 +823,11 @@ public function mobileAppUserSettings(Request $request, Extensions $extension) $response = appsGetConnections($org_id); // If successful continue - if (isset($response['result'])){ + if (isset($response['result'])) { $connections = $response['result']; $app_domain = $response['result'][0]['domain']; - // Otherwise return failed status + // Otherwise return failed status } elseif (isset($response['error'])) { return response()->json([ 'status' => 401, @@ -778,7 +906,7 @@ public function createUser(Request $request) // If success and user is activated send user email with credentials if ($response['result']['status'] == 1) { - if($hidePassInEmail == 'true' && $request->activate == 'on') { + if ($hidePassInEmail == 'true' && $request->activate == 'on') { // Include get-password link and remove password value $passwordToken = Str::random(40); MobileAppPasswordResetLinks::where('extension_uuid', $extension->extension_uuid)->delete(); @@ -791,7 +919,7 @@ public function createUser(Request $request) $includePasswordUrl = $passwordUrlShow == 'true' ? route('appsGetPasswordByToken', $passwordToken) : null; $response['result']['password_url'] = $includePasswordUrl; } - if(isset($extension->voicemail->voicemail_mail_to)) { + if (isset($extension->voicemail->voicemail_mail_to)) { SendAppCredentials::dispatch($response['result'])->onQueue('emails'); } } @@ -811,11 +939,11 @@ public function createUser(Request $request) // Log::info($response); $qrcode = ""; - if($hidePassInEmail == 'false') { - if ($request->activate == 'on'){ + if ($hidePassInEmail == 'false') { + if ($request->activate == 'on') { // Generate QR code $qrcode = QrCode::format('png')->generate('{"domain":"' . $response['result']['domain'] . - '","username":"' .$response['result']['username'] . '","password":"'. $response['result']['password'] . '"}'); + '","username":"' . $response['result']['username'] . '","password":"' . $response['result']['password'] . '"}'); } } else { $response['result']['password'] = null; @@ -823,7 +951,7 @@ public function createUser(Request $request) return response()->json([ 'user' => $response['result'], - 'qrcode' => ($qrcode!= "") ? base64_encode($qrcode) : null, + 'qrcode' => ($qrcode != "") ? base64_encode($qrcode) : null, 'status' => 'success', 'message' => 'The user has been successfully created' ]); @@ -910,7 +1038,7 @@ public function resetPassword(Request $request, Extensions $extension) } // If success and user is activated send user email with credentials - if($hidePassInEmail == 'true') { + if ($hidePassInEmail == 'true') { // Include get-password link and remove password value $passwordToken = Str::random(40); MobileAppPasswordResetLinks::where('extension_uuid', $extension->extension_uuid)->delete(); @@ -927,17 +1055,17 @@ public function resetPassword(Request $request, Extensions $extension) } $qrcode = ""; - if($hidePassInEmail == 'false') { + if ($hidePassInEmail == 'false') { // Generate QR code $qrcode = QrCode::format('png')->generate('{"domain":"' . $response['result']['domain'] . - '","username":"' .$response['result']['username'] . '","password":"'. $response['result']['password'] . '"}'); + '","username":"' . $response['result']['username'] . '","password":"' . $response['result']['password'] . '"}'); } else { $response['result']['password'] = null; } return response()->json([ 'user' => $response['result'], - 'qrcode' => ($qrcode!= "") ? base64_encode($qrcode) : null, + 'qrcode' => ($qrcode != "") ? base64_encode($qrcode) : null, 'status' => 200, 'success' => [ 'message' => 'The mobile app password was successfully reset' @@ -970,7 +1098,7 @@ public function setStatus(Request $request, Extensions $extension) $appUser = $extension->mobile_app; - if ($mobile_app['status']==1) { + if ($mobile_app['status'] == 1) { // Send request to update user settings $response = appsUpdateUser($mobile_app); @@ -997,8 +1125,7 @@ public function setStatus(Request $request, Extensions $extension) $appUser->status = $mobile_app['status']; $appUser->save(); } - - } else if ($mobile_app['status']==-1) { + } else if ($mobile_app['status'] == -1) { // Send request to delete user first and then recreate it $response = appsDeleteUser($mobile_app['org_id'], $mobile_app['user_id']); @@ -1078,7 +1205,6 @@ public function setStatus(Request $request, Extensions $extension) $appUser->user_id = $response['result']['id']; $appUser->status = $response['result']['status']; $appUser->save(); - } @@ -1149,7 +1275,8 @@ public function destroy($id) // } - public function emailUser (){ + public function emailUser() + { //Mail::to("info@nemerald.com")->send(new AppCredentialsGenerated()); SendAppCredentials::dispatch()->onQueue('emails'); diff --git a/app/Models/DefaultSettings.php b/app/Models/DefaultSettings.php index aaa8320b..dd3d20d3 100644 --- a/app/Models/DefaultSettings.php +++ b/app/Models/DefaultSettings.php @@ -8,7 +8,7 @@ class DefaultSettings extends Model { use HasFactory, \App\Models\Traits\TraitUuid; - + protected $table = "v_default_settings"; public $timestamps = false; @@ -17,5 +17,19 @@ class DefaultSettings extends Model public $incrementing = false; protected $keyType = 'string'; - + // Add fields to allow mass assignment + protected $fillable = [ + 'default_setting_uuid', + 'default_setting_category', + 'default_setting_subcategory', + 'default_setting_name', + 'default_setting_value', + 'default_setting_order', + 'default_setting_enabled', + 'default_setting_description', + 'insert_date', + 'insert_user', + 'update_date', + 'update_user', + ]; } diff --git a/app/Models/Domain.php b/app/Models/Domain.php index 88b86007..984da6f3 100644 --- a/app/Models/Domain.php +++ b/app/Models/Domain.php @@ -30,6 +30,7 @@ class Domain extends Model 'domain_description' ]; + /** * Get the settings for the domain. */ diff --git a/app/helpers.php b/app/helpers.php index 870d8238..5724370b 100644 --- a/app/helpers.php +++ b/app/helpers.php @@ -1210,7 +1210,7 @@ function get_local_time_zone($domain_uuid) function get_domain_setting($setting_name, $domain_uuid = null) { if (!$domain_uuid) { - $domain_uuid = Session::get('domain_uuid'); + $domain_uuid = session('domain_uuid'); } $setting = DomainSettings::where('domain_uuid', $domain_uuid) diff --git a/config/version.php b/config/version.php index 1c45cc67..03e4d2e3 100644 --- a/config/version.php +++ b/config/version.php @@ -2,6 +2,6 @@ return [ - 'release' => '0.9.15', + 'release' => '0.9.16', ]; \ No newline at end of file diff --git a/resources/js/Pages/RingotelAppSettings.vue b/resources/js/Pages/RingotelAppSettings.vue new file mode 100644 index 00000000..0dc5f6f7 --- /dev/null +++ b/resources/js/Pages/RingotelAppSettings.vue @@ -0,0 +1,565 @@ + + + + + diff --git a/resources/views/layouts/apps/list.blade.php b/resources/views/layouts/apps/list.blade.php index 4e0ecf77..4b50edcd 100644 --- a/resources/views/layouts/apps/list.blade.php +++ b/resources/views/layouts/apps/list.blade.php @@ -204,13 +204,17 @@ class="action-icon">
- + + + + + + + + + +
@@ -227,7 +231,7 @@ class="action-icon">
- If this option is enabled, users within this organization will need to follow a one-time link to retrieve their app password. + If this option is enabled, users in this organization must use a one-time link to access their app password. diff --git a/routes/web.php b/routes/web.php index bb98c538..581524fd 100644 --- a/routes/web.php +++ b/routes/web.php @@ -216,7 +216,8 @@ Route::get('/voicemails/{voicemail}/greetings/{filename}/delete', [VoicemailController::class, 'deleteVoicemailGreeting'])->name('deleteVoicemailGreeting'); //Apps - Route::get('/apps', [AppsController::class, 'index'])->name('appsStatus'); + Route::resource('apps', AppsController::class); + Route::post('apps/item-options', [AppsController::class, 'getItemOptions'])->name('apps.item.options'); Route::post('/apps/organization/create', [AppsController::class, 'createOrganization'])->name('appsCreateOrganization'); Route::delete('/apps/organization/{domain}', [AppsController::class, 'destroyOrganization'])->name('appsDestroyOrganization'); Route::get('/apps/organization/', [AppsController::class, 'getOrganizations'])->name('appsGetOrganizations'); From 4c74e60aa3156569ddfa4331970567dc3ef4e216 Mon Sep 17 00:00:00 2001 From: nemerald-voip Date: Tue, 17 Dec 2024 00:06:32 -0800 Subject: [PATCH 02/19] added activation modal --- app/Http/Controllers/AppsController.php | 117 ++++++++- app/Http/Controllers/VoicemailController.php | 2 +- resources/js/Pages/RingotelAppSettings.vue | 87 ++----- .../forms/CreateRingotelOrgForm.vue | 235 ++++++++++++++++++ .../js/Pages/components/icons/SyncAltIcon.vue | 12 + 5 files changed, 389 insertions(+), 64 deletions(-) create mode 100644 resources/js/Pages/components/forms/CreateRingotelOrgForm.vue create mode 100644 resources/js/Pages/components/icons/SyncAltIcon.vue diff --git a/app/Http/Controllers/AppsController.php b/app/Http/Controllers/AppsController.php index 7a597f35..d35da8f1 100644 --- a/app/Http/Controllers/AppsController.php +++ b/app/Http/Controllers/AppsController.php @@ -55,7 +55,6 @@ public function index() ] ] ); - } /** @@ -163,6 +162,122 @@ protected function filterSearch($query, $value) }); } + public function getItemOptions() + { + try { + + $domain_uuid = request('domain_uuid') ?? session('domain_uuid'); + $item_uuid = request('item_uuid'); // Retrieve item_uuid from the request + + // Base navigation array without Greetings + $navigation = [ + [ + 'name' => 'Organization', + 'icon' => 'BuildingOfficeIcon', + 'slug' => 'organization', + ], + [ + 'name' => 'Connections', + 'icon' => 'SyncAltIcon', + 'slug' => 'connections', + ], + ]; + + + $routes = []; + + $regions = [ + ['value' => '1', 'name' => 'US East'], + ['value' => '2', 'name' => 'US West'], + ['value' => '3', 'name' => 'Europe (Frankfurt)'], + ['value' => '4', 'name' => 'Asia Pacific (Singapore)'], + ['value' => '5', 'name' => 'Europe (London)'], + ['value' => '6', 'name' => 'India'], + ['value' => '7', 'name' => 'Australia'], + ['value' => '8', 'name' => 'Europe (Dublin)'], + ['value' => '9', 'name' => 'Canada (Central)'], + ['value' => '10', 'name' => 'South Africa'], + ]; + + // Check if item_uuid exists to find an existing model + if ($item_uuid) { + // Find existing model by item_uuid + $model = $this->model + ->select( + 'domain_uuid', + 'domain_name', + 'domain_description', + ) + ->with(['settings' => function ($query) { + $query->select('domain_uuid', 'domain_setting_uuid', 'domain_setting_category', 'domain_setting_subcategory', 'domain_setting_value') + ->where('domain_setting_category', 'app shell') + ->where('domain_setting_subcategory', 'org_id') + ->where('domain_setting_enabled', true); + }])->where($this->model->getKeyName(), $item_uuid)->first(); + + + $model->ringotel_status = $model->settings() + ->where('domain_setting_category', 'app shell') + ->where('domain_setting_subcategory', 'org_id') + ->where('domain_setting_enabled', true) + ->exists() ? 'true' : 'false'; + // logger($model); + + // If model doesn't exist throw an error + if (!$model) { + throw new \Exception("Failed to fetch item details. Item not found"); + } + + + $routes = array_merge($routes, [ + + ]); + } else { + // Create a new voicemail if item_uuid is not provided + $voicemail = $this->model; + $voicemail->voicemail_id = $voicemail->generateUniqueSequenceNumber(); + $voicemail->voicemail_password = $voicemail->voicemail_id; + $voicemail->voicemail_file = get_domain_setting('voicemail_file'); + $voicemail->voicemail_local_after_email = get_domain_setting('keep_local'); + $voicemail->voicemail_transcription_enabled = get_domain_setting('transcription_enabled_default'); + $voicemail->voicemail_tutorial = 'false'; + $voicemail->voicemail_enabled = 'true'; + $voicemail->voicemail_recording_instructions = 'true'; + } + + $permissions = $this->getUserPermissions(); + + + // Construct the itemOptions object + $itemOptions = [ + 'navigation' => $navigation, + 'model' => $model, + 'regions' => $regions, + 'permissions' => $permissions, + 'routes' => $routes, + // Define options for other fields as needed + ]; + + return $itemOptions; + } catch (\Exception $e) { + // Log the error message + logger($e->getMessage() . " at " . $e->getFile() . ":" . $e->getLine()); + // report($e); + + // Handle any other exception that may occur + return response()->json([ + 'success' => false, + 'errors' => ['server' => ['Failed to fetch item details']] + ], 500); // 500 Internal Server Error for any other errors + } + } + + public function getUserPermissions() + { + $permissions = []; + return $permissions; + } + /** * Submit request to create a new organization to Ringotel * diff --git a/app/Http/Controllers/VoicemailController.php b/app/Http/Controllers/VoicemailController.php index c01766f9..009d4226 100644 --- a/app/Http/Controllers/VoicemailController.php +++ b/app/Http/Controllers/VoicemailController.php @@ -534,7 +534,7 @@ public function getItemOptions() $routes = []; - // Check if item_uuid exists to find an existing voicemail + // Check if item_uuid exists to find an existing model if ($item_uuid) { // Find existing voicemail by item_uuid $voicemail = Voicemails::with([ diff --git a/resources/js/Pages/RingotelAppSettings.vue b/resources/js/Pages/RingotelAppSettings.vue index 0dc5f6f7..a47bda76 100644 --- a/resources/js/Pages/RingotelAppSettings.vue +++ b/resources/js/Pages/RingotelAppSettings.vue @@ -72,13 +72,11 @@ diff --git a/resources/js/Pages/components/general/RingotelConnections.vue b/resources/js/Pages/components/general/RingotelConnections.vue new file mode 100644 index 00000000..b4dc6567 --- /dev/null +++ b/resources/js/Pages/components/general/RingotelConnections.vue @@ -0,0 +1,246 @@ + + + diff --git a/resources/js/Pages/components/modal/RingotelConnectionModal.vue b/resources/js/Pages/components/modal/RingotelConnectionModal.vue new file mode 100644 index 00000000..a910f9bc --- /dev/null +++ b/resources/js/Pages/components/modal/RingotelConnectionModal.vue @@ -0,0 +1,224 @@ + + + \ No newline at end of file From 176df5dc6bb9336050264fc03a0270e48e5ac48c Mon Sep 17 00:00:00 2001 From: nemerald-voip Date: Wed, 18 Dec 2024 21:53:51 -0800 Subject: [PATCH 05/19] From server --- resources/js/Pages/RingotelAppSettings.vue | 4 +- .../forms/CreateRingotelConnectionForm.vue | 313 ++++++++++++++++++ .../forms/CreateRingotelOrgForm.vue | 42 ++- .../modal/RingotelConnectionModal.vue | 224 ------------- 4 files changed, 351 insertions(+), 232 deletions(-) create mode 100644 resources/js/Pages/components/forms/CreateRingotelConnectionForm.vue delete mode 100644 resources/js/Pages/components/modal/RingotelConnectionModal.vue diff --git a/resources/js/Pages/RingotelAppSettings.vue b/resources/js/Pages/RingotelAppSettings.vue index 44890cb6..a0d5b330 100644 --- a/resources/js/Pages/RingotelAppSettings.vue +++ b/resources/js/Pages/RingotelAppSettings.vue @@ -187,8 +187,7 @@ - + \ No newline at end of file diff --git a/resources/js/Pages/components/forms/CreateRingotelOrgForm.vue b/resources/js/Pages/components/forms/CreateRingotelOrgForm.vue index e4c137a3..f1fdc31b 100644 --- a/resources/js/Pages/components/forms/CreateRingotelOrgForm.vue +++ b/resources/js/Pages/components/forms/CreateRingotelOrgForm.vue @@ -133,9 +133,13 @@ - - + + + @@ -160,7 +164,12 @@ import { InformationCircleIcon } from "@heroicons/vue/24/outline"; import { ExclamationCircleIcon } from '@heroicons/vue/20/solid' import { BuildingOfficeIcon } from '@heroicons/vue/24/outline'; import RingotelConnections from "../general/RingotelConnections.vue"; -import RingotelConnectionModal from "../modal/RingotelConnectionModal.vue"; +import AddEditItemModal from "../modal/AddEditItemModal.vue"; +import CreateRingotelConnectionForm from "../forms/CreateRingotelConnectionForm.vue"; + +const ringotelConnectionFormSubmiting = ref(null); +const loadingModal = ref(false); +const formErrors = ref(null); const props = defineProps({ options: Object, @@ -169,7 +178,6 @@ const props = defineProps({ errors: Object, }); - // Initialize activeTab with the currently active tab from props const activeTab = ref(props.activeTab || props.options.navigation[0].slug); @@ -226,5 +234,29 @@ const handleAddConnection = (selected) => { showConnectionModal.value = true; } +const handleCreateConnectionRequest = (form) => { + ringotelConnectionFormSubmiting.value = true; + formErrors.value = null; + + // axios.post(props.routes.create_organization, form) + // .then((response) => { + // ringotelConnectionFormSubmiting.value = false; + // showNotification('success', response.data.messages); + // activationActiveTab.value = 'connections'; + // // handleSearchButtonClick(); + // // handleModalClose(); + // // handleClearSelection(); + // }).catch((error) => { + // ringotelConnectionFormSubmiting.value = false; + // handleClearSelection(); + // handleFormErrorResponse(error); + // }); + +}; + +const handleModalClose = () => { + showConnectionModal.value = false; +} + diff --git a/resources/js/Pages/components/modal/RingotelConnectionModal.vue b/resources/js/Pages/components/modal/RingotelConnectionModal.vue deleted file mode 100644 index a910f9bc..00000000 --- a/resources/js/Pages/components/modal/RingotelConnectionModal.vue +++ /dev/null @@ -1,224 +0,0 @@ - - - \ No newline at end of file From 1fd18175c50488e5619a5cdff48162af8d3f6eb6 Mon Sep 17 00:00:00 2001 From: Alexander S Date: Thu, 19 Dec 2024 12:28:32 +0600 Subject: [PATCH 06/19] Fix wrong domain name on GetPassword UI --- app/Http/Controllers/AppsController.php | 11 +++--- .../Controllers/AppsCredentialsController.php | 4 +- ..._mobile_app_password_reset_links_table.php | 37 +++++++++++++++++++ 3 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 database/migrations/2024_12_19_324567_add_domain_to_mobile_app_password_reset_links_table.php diff --git a/app/Http/Controllers/AppsController.php b/app/Http/Controllers/AppsController.php index cf0a258e..86cd1524 100644 --- a/app/Http/Controllers/AppsController.php +++ b/app/Http/Controllers/AppsController.php @@ -282,7 +282,7 @@ public function getUserPermissions() } /** - * Submit API request to Ringotel to create a new organization + * Submit API request to Ringotel to create a new organization * * @return \Illuminate\Http\JsonResponse */ @@ -293,7 +293,7 @@ public function createOrganization(StoreRingotelActivationRequest $request, Ring $inputs = $request->validated(); try { - // Send API request to create organization + // Send API request to create organization $organization = $this->ringotelApiService->createOrganization($inputs); @@ -332,7 +332,7 @@ public function createOrganization(StoreRingotelActivationRequest $request, Ring // Return a JSON response indicating success return response()->json([ - 'messages' => ['success' => ['Organization succesfully activated']] + 'messages' => ['success' => ['Organization successfully activated']] ], 201); } catch (\Exception $e) { logger($e->getMessage() . " at " . $e->getFile() . ":" . $e->getLine()); @@ -650,7 +650,7 @@ public function createConnection(Request $request) 'connection_domain' => $request->connection_domain, 'org_id' => $request->org_id, 'conn_id' => $response['result']['id'], - 'message' => 'Connection was created succesfully, but unable to store Conn ID in database', + 'message' => 'Connection was created successfully, but unable to store Conn ID in database', ]); } @@ -659,7 +659,7 @@ public function createConnection(Request $request) 'connection_domain' => $request->connection_domain, 'org_id' => $request->org_id, 'conn_id' => $response['result']['id'], - 'message' => 'Connection created succesfully', + 'message' => 'Connection created successfully', ]); // Otherwise return failed status } elseif (isset($response['error'])) { @@ -1025,6 +1025,7 @@ public function createUser(Request $request) $appCredentials = new MobileAppPasswordResetLinks(); $appCredentials->token = $passwordToken; $appCredentials->extension_uuid = $extension->extension_uuid; + $appCredentials->domain = $response['result']['domain']; $appCredentials->save(); $passwordUrlShow = userCheckPermission('mobile_apps_password_url_show') ?? 'false'; diff --git a/app/Http/Controllers/AppsCredentialsController.php b/app/Http/Controllers/AppsCredentialsController.php index 926c7445..f3da493e 100644 --- a/app/Http/Controllers/AppsCredentialsController.php +++ b/app/Http/Controllers/AppsCredentialsController.php @@ -26,12 +26,12 @@ public function getPasswordByToken(Request $request): \Inertia\Response } $extension = $appCredentials->extension()->first(); - $extensionDomain = $extension->domain()->first(); + //$extensionDomain = $extension->domain()->first(); return Inertia::render('Auth/MobileAppGetPassword', [ 'display_name' => $extension->effective_caller_id_name, - 'domain' => $extensionDomain->domain_name, + 'domain' => $appCredentials->domain, 'username' => $extension->extension, 'extension' => $extension->extension, 'routes' => [ diff --git a/database/migrations/2024_12_19_324567_add_domain_to_mobile_app_password_reset_links_table.php b/database/migrations/2024_12_19_324567_add_domain_to_mobile_app_password_reset_links_table.php new file mode 100644 index 00000000..c84092ba --- /dev/null +++ b/database/migrations/2024_12_19_324567_add_domain_to_mobile_app_password_reset_links_table.php @@ -0,0 +1,37 @@ +string('domain')->nullable()->after('token'); + }); + } + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + if (Schema::hasColumn('mobile_app_password_reset_links', 'domain')) { + Schema::table('mobile_app_password_reset_links', function (Blueprint $table) { + $table->dropColumn('domain'); + }); + } + } +} + From 2dd548961f2451a553bd18ba23de3a28aba4700f Mon Sep 17 00:00:00 2001 From: Alexander S Date: Thu, 19 Dec 2024 12:35:10 +0600 Subject: [PATCH 07/19] Remove commented-out code in AppsCredentialsController --- app/Http/Controllers/AppsCredentialsController.php | 1 - 1 file changed, 1 deletion(-) diff --git a/app/Http/Controllers/AppsCredentialsController.php b/app/Http/Controllers/AppsCredentialsController.php index f3da493e..72e3de54 100644 --- a/app/Http/Controllers/AppsCredentialsController.php +++ b/app/Http/Controllers/AppsCredentialsController.php @@ -26,7 +26,6 @@ public function getPasswordByToken(Request $request): \Inertia\Response } $extension = $appCredentials->extension()->first(); - //$extensionDomain = $extension->domain()->first(); return Inertia::render('Auth/MobileAppGetPassword', [ From 81bcfdaad7580e29d5849ce1a9a2415caefd91b8 Mon Sep 17 00:00:00 2001 From: Alexander S Date: Thu, 19 Dec 2024 12:44:02 +0600 Subject: [PATCH 08/19] Add domain field to app credentials saving logic on reset password --- app/Http/Controllers/AppsController.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Http/Controllers/AppsController.php b/app/Http/Controllers/AppsController.php index 86cd1524..1b6a751d 100644 --- a/app/Http/Controllers/AppsController.php +++ b/app/Http/Controllers/AppsController.php @@ -1158,6 +1158,7 @@ public function resetPassword(Request $request, Extensions $extension) $appCredentials = new MobileAppPasswordResetLinks(); $appCredentials->token = $passwordToken; $appCredentials->extension_uuid = $extension->extension_uuid; + $appCredentials->domain = $response['result']['domain']; $appCredentials->save(); $passwordUrlShow = userCheckPermission('mobile_apps_password_url_show') ?? 'false'; $includePasswordUrl = $passwordUrlShow == 'true' ? route('appsGetPasswordByToken', $passwordToken) : null; From 70fa6b96f6a01f792698c2cc13873f1413b14a63 Mon Sep 17 00:00:00 2001 From: nemerald-voip Date: Wed, 18 Dec 2024 23:45:04 -0800 Subject: [PATCH 09/19] added connection modal --- app/Http/Controllers/AppsController.php | 36 +++- resources/js/Pages/RingotelAppSettings.vue | 1 + .../forms/CreateRingotelConnectionForm.vue | 202 +++++------------- .../forms/CreateRingotelOrgForm.vue | 3 +- 4 files changed, 83 insertions(+), 159 deletions(-) diff --git a/app/Http/Controllers/AppsController.php b/app/Http/Controllers/AppsController.php index 86cd1524..f005adf2 100644 --- a/app/Http/Controllers/AppsController.php +++ b/app/Http/Controllers/AppsController.php @@ -160,8 +160,6 @@ protected function filterSearch($query, $value) public function getItemOptions() { try { - - $domain_uuid = request('domain_uuid') ?? session('domain_uuid'); $item_uuid = request('item_uuid'); // Retrieve item_uuid from the request // Base navigation array without Greetings @@ -199,6 +197,13 @@ public function getItemOptions() ['value' => '2', 'name' => 'Pro Package'], ]; + $protocols = [ + ['value' => 'sip', 'name' => 'UDP'], + ['value' => 'sip-tcp', 'name' => 'TCP'], + ['value' => 'sips', 'name' => 'TLS'], + ['value' => 'DNS-NAPTR', 'name' => 'DNS-NAPTR'], + ]; + // Check if item_uuid exists to find an existing model if ($item_uuid) { // Find existing model by item_uuid @@ -239,6 +244,9 @@ public function getItemOptions() $package = get_domain_setting('package', $model->domain_uuid); $dont_send_user_credentials = get_domain_setting('dont_send_user_credentials', $model->domain_uuid); $org_id = get_domain_setting('org_id', $model->domain_uuid); + $protocol = get_domain_setting('mobile_app_conn_protocol', $model->domain_uuid); + $port = get_domain_setting('line_sip_port', $model->domain_uuid); + $proxy = get_domain_setting('mobile_app_proxy', $model->domain_uuid); logger($org_id); if (!$org_id) { @@ -251,6 +259,7 @@ public function getItemOptions() 'model' => $model, 'regions' => $regions, 'packages' => $packages, + 'protocols' => $protocols, 'permissions' => $permissions, 'routes' => $routes, 'suggested_ringotel_domain' => $suggested_ringotel_domain, @@ -258,6 +267,9 @@ public function getItemOptions() 'default_package' => $package, 'dont_send_user_credentials' => $dont_send_user_credentials, 'connections' => $connections, + 'default_protocol' => $protocol, + 'default_port' => $port, + 'default_proxy' => $proxy, // Define options for other fields as needed ]; @@ -296,18 +308,15 @@ public function createOrganization(StoreRingotelActivationRequest $request, Ring // Send API request to create organization $organization = $this->ringotelApiService->createOrganization($inputs); - - // Check for existing records with a different value + // Check for existing records $existingSetting = DomainSettings::where('domain_uuid', $inputs['domain_uuid']) ->where('domain_setting_category', 'app shell') ->where('domain_setting_subcategory', 'org_id') ->first(); if ($existingSetting) { - if ($existingSetting->domain_setting_value !== $organization->org_id) { - // Delete the existing record if the value is different - $existingSetting->delete(); - } + // Delete the existing record + $existingSetting->delete(); } // Save the new record @@ -320,6 +329,16 @@ public function createOrganization(StoreRingotelActivationRequest $request, Ring 'domain_setting_enabled' => true, ]); + // Check for existing records + $existingSetting = DomainSettings::where('domain_uuid', $inputs['domain_uuid']) + ->where('domain_setting_category', 'mobile_apps') + ->where('domain_setting_subcategory', 'dont_send_user_credentials') + ->first(); + + if ($existingSetting) { + $existingSetting->delete(); + } + $domainSetting = DomainSettings::create([ 'domain_uuid' => $inputs['domain_uuid'], 'domain_setting_category' => 'mobile_apps', @@ -332,6 +351,7 @@ public function createOrganization(StoreRingotelActivationRequest $request, Ring // Return a JSON response indicating success return response()->json([ + 'org_id' => $organization['id'], 'messages' => ['success' => ['Organization successfully activated']] ], 201); } catch (\Exception $e) { diff --git a/resources/js/Pages/RingotelAppSettings.vue b/resources/js/Pages/RingotelAppSettings.vue index a0d5b330..edc97e21 100644 --- a/resources/js/Pages/RingotelAppSettings.vue +++ b/resources/js/Pages/RingotelAppSettings.vue @@ -289,6 +289,7 @@ const handleCreateRequest = (form) => { .then((response) => { activateFormSubmiting.value = false; showNotification('success', response.data.messages); + itemOptions.value.orgId = response.data.org_id; activationActiveTab.value = 'connections'; // handleSearchButtonClick(); // handleModalClose(); diff --git a/resources/js/Pages/components/forms/CreateRingotelConnectionForm.vue b/resources/js/Pages/components/forms/CreateRingotelConnectionForm.vue index 0f5e9ce9..7b3daeeb 100644 --- a/resources/js/Pages/components/forms/CreateRingotelConnectionForm.vue +++ b/resources/js/Pages/components/forms/CreateRingotelConnectionForm.vue @@ -1,6 +1,6 @@ @@ -105,7 +105,7 @@
-
@@ -159,19 +159,19 @@ :loading="loadingModal" @close="handleModalClose"> - + @@ -210,7 +210,7 @@ import BulkUpdateDeviceForm from "./components/forms/BulkUpdateDeviceForm.vue"; import BulkActionButton from "./components/general/BulkActionButton.vue"; import MainLayout from "../Layouts/MainLayout.vue"; import CreateRingotelOrgForm from "./components/forms/CreateRingotelOrgForm.vue"; -import UpdateVoicemailForm from "./components/forms/UpdateVoicemailForm.vue"; +import UpdateRingotelOrgForm from "./components/forms/UpdateRingotelOrgForm.vue"; import Notification from "./components/notifications/Notification.vue"; import Badge from "@generalComponents/Badge.vue"; import LinkOffIcon from "@icons/LinkOffIcon.vue"; @@ -223,8 +223,8 @@ const loadingModal = ref(false) const selectAll = ref(false); const selectedItems = ref([]); const selectPageItems = ref(false); -const createModalTrigger = ref(false); const showActivateModal = ref(false); +const showEditModal = ref(false); const bulkUpdateModalTrigger = ref(false); const confirmationModalTrigger = ref(false); const confirmationModalDestroyPath = ref(null); @@ -275,8 +275,8 @@ const handleActivateButtonClick = (itemUuid) => { getItemOptions(itemUuid); } -const handleEditRequest = (itemUuid) => { - editModalTrigger.value = true +const handleEditButtonClick = (itemUuid) => { + showEditModal.value = true formErrors.value = null; loadingModal.value = true getItemOptions(itemUuid); @@ -366,11 +366,16 @@ const handleBulkActionRequest = (action) => { } -const handleCreateButtonClick = () => { - createModalTrigger.value = true - formErrors.value = null; - loadingModal.value = true - getItemOptions(); +// const handleCreateButtonClick = () => { +// createModalTrigger.value = true +// formErrors.value = null; +// loadingModal.value = true +// getItemOptions(); +// } + +const handleActivationFinish = () => { + handleModalClose(); + handleSearchButtonClick(); } const handleSelectAll = () => { @@ -509,8 +514,8 @@ const handleClearSelection = () => { } const handleModalClose = () => { - createModalTrigger.value = false; showActivateModal.value = false; + showEditModal.value = false, confirmationModalTrigger.value = false; bulkUpdateModalTrigger.value = false; } diff --git a/resources/js/Pages/components/forms/CreateRingotelOrgForm.vue b/resources/js/Pages/components/forms/CreateRingotelOrgForm.vue index 35c73ed0..f5236117 100644 --- a/resources/js/Pages/components/forms/CreateRingotelOrgForm.vue +++ b/resources/js/Pages/components/forms/CreateRingotelOrgForm.vue @@ -119,11 +119,22 @@ + @delete-connection="handleDeleteConnectionRequest" + :isDeleting="showConnectionDeletingStatus" /> +
+ + +
+ @@ -194,6 +205,7 @@ const setActiveTab = (tabSlug) => { }; const showConnectionModal = ref(false); +const showConnectionDeletingStatus = ref(false); // Map icon names to their respective components @@ -231,6 +243,10 @@ const submitForm = () => { emits('submit', form); // Emit the event with the form data } +const handleFinishButtonClick = () => { + emits('cancel'); +} + const handleUpdateRegionField = (selected) => { form.region = selected.value; } @@ -273,21 +289,22 @@ const handleCreateConnectionRequest = (form) => { }; const handleDeleteConnectionRequest = (connection) => { - console.log(connection); - // ringotelConnectionFormSubmiting.value = true; + showConnectionDeletingStatus.value = true; // emits('clear-errors'); axios.post(props.options.routes.delete_connection, connection) .then((response) => { - ringotelConnectionFormSubmiting.value = false; + showConnectionDeletingStatus.value = false; emits('success', response.data.messages); const updatedConnections = connections.value.filter( (conn) => conn.conn_id !== connection.conn_id ); connections.value = updatedConnections; + console.log(connections.value); }).catch((error) => { + showConnectionDeletingStatus.value = false; emits('error', error); // Emit the event with error }); diff --git a/resources/js/Pages/components/forms/UpdateRingotelOrgForm.vue b/resources/js/Pages/components/forms/UpdateRingotelOrgForm.vue new file mode 100644 index 00000000..e22f92f8 --- /dev/null +++ b/resources/js/Pages/components/forms/UpdateRingotelOrgForm.vue @@ -0,0 +1,318 @@ + + + + diff --git a/resources/js/Pages/components/general/RingotelConnections.vue b/resources/js/Pages/components/general/RingotelConnections.vue index 623b25f6..0325da1f 100644 --- a/resources/js/Pages/components/general/RingotelConnections.vue +++ b/resources/js/Pages/components/general/RingotelConnections.vue @@ -51,13 +51,16 @@
- + Delete + :class="[active ? 'bg-gray-100 text-gray-900' : 'text-gray-700', 'flex px-4 py-2 text-sm']"> + Delete + +
@@ -93,12 +96,14 @@ import ComboBox from "../general/ComboBox.vue"; import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue' import { EllipsisVerticalIcon } from '@heroicons/vue/24/outline'; import InputField from "../general/InputField.vue"; +import Spinner from "../general/Spinner.vue"; const props = defineProps({ modelValue: [Object, null], routingTypes: [Object, null], optionsUrl: String, + isDeleting: Boolean, }); diff --git a/routes/web.php b/routes/web.php index 9ea44900..390591f2 100644 --- a/routes/web.php +++ b/routes/web.php @@ -220,7 +220,7 @@ Route::post('apps/item-options', [AppsController::class, 'getItemOptions'])->name('apps.item.options'); Route::post('/apps/organization/create', [AppsController::class, 'createOrganization'])->name('apps.organization.create'); Route::delete('/apps/organization/{domain}', [AppsController::class, 'destroyOrganization'])->name('appsDestroyOrganization'); - Route::get('/apps/organization/', [AppsController::class, 'getOrganizations'])->name('appsGetOrganizations'); + // Route::get('/apps/organization/', [AppsController::class, 'getOrganizations'])->name('appsGetOrganizations'); Route::post('/apps/organization/sync', [AppsController::class, 'syncOrganizations'])->name('appsSyncOrganizations'); Route::post('/apps/users/{extension}', [AppsController::class, 'mobileAppUserSettings'])->name('mobileAppUserSettings'); //Route::get('/apps/organization/update', [AppsController::class, 'updateOrganization']) ->name('appsUpdateOrganization'); From 736f87303251a1d76521196be360384689fee892 Mon Sep 17 00:00:00 2001 From: nemerald-voip Date: Fri, 27 Dec 2024 14:39:21 -0800 Subject: [PATCH 17/19] added connection update form --- app/Http/Controllers/AppsController.php | 77 ++- .../UpdateRingotelConnectionRequest.php | 114 ++++ .../UpdateRingotelOrganizationRequest.php | 88 +++ app/Services/RingotelApiService.php | 175 +++++- resources/js/Pages/RingotelAppSettings.vue | 8 +- .../forms/CreateRingotelOrgForm.vue | 62 +- .../forms/UpdateRingotelConnectionForm.vue | 581 ++++++++++++++++++ .../forms/UpdateRingotelOrgForm.vue | 88 ++- .../general/RingotelConnections.vue | 12 +- routes/web.php | 2 + 10 files changed, 1162 insertions(+), 45 deletions(-) create mode 100644 app/Http/Requests/UpdateRingotelConnectionRequest.php create mode 100644 app/Http/Requests/UpdateRingotelOrganizationRequest.php create mode 100644 resources/js/Pages/components/forms/UpdateRingotelConnectionForm.vue diff --git a/app/Http/Controllers/AppsController.php b/app/Http/Controllers/AppsController.php index 911731c6..61409b8e 100644 --- a/app/Http/Controllers/AppsController.php +++ b/app/Http/Controllers/AppsController.php @@ -13,12 +13,13 @@ use App\Jobs\SendAppCredentials; use Illuminate\Support\Facades\Log; use App\Services\RingotelApiService; -use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Session; use App\Models\MobileAppPasswordResetLinks; use SimpleSoftwareIO\QrCode\Facades\QrCode; -use App\Http\Requests\StoreRingotelOrganizationRequest; use App\Http\Requests\StoreRingotelConnectionRequest; +use App\Http\Requests\UpdateRingotelConnectionRequest; +use App\Http\Requests\StoreRingotelOrganizationRequest; +use App\Http\Requests\UpdateRingotelOrganizationRequest; class AppsController extends Controller { @@ -54,8 +55,7 @@ public function index() 'routes' => [ 'current_page' => route('apps.index'), 'create_organization' => route('apps.organization.create'), - // 'create_connection' => route('apps.connection.create'), - // 'delete_connection' => route('apps.connection.destroy'), + 'update_organization' => route('apps.organization.update'), 'item_options' => route('apps.item.options'), ] ] @@ -230,6 +230,7 @@ public function getItemOptions(RingotelApiService $ringotelApiService) $routes = [ 'create_connection' => route('apps.connection.create'), + 'update_connection' => route('apps.connection.update'), 'delete_connection' => route('apps.connection.destroy'), ]; @@ -321,6 +322,7 @@ public function getItemOptions(RingotelApiService $ringotelApiService) 'conn_navigation' => $conn_navigation, 'model' => $model ?? null, 'organization' => $organization ?? null, + 'orgId' => $organization->id ?? null, 'regions' => $regions, 'packages' => $packages, 'protocols' => $protocols, @@ -444,6 +446,35 @@ public function createOrganization(StoreRingotelOrganizationRequest $request, Ri } } + /** + * Submit API request to Ringotel to create a new organization + * + * @return \Illuminate\Http\JsonResponse + */ + public function UpdateOrganization(UpdateRingotelOrganizationRequest $request, RingotelApiService $ringotelApiService) + { + $this->ringotelApiService = $ringotelApiService; + + $inputs = $request->validated(); + + try { + // Send API request to update organization + $organization = $this->ringotelApiService->updateOrganization($inputs); + + // Return a JSON response indicating success + return response()->json([ + 'messages' => ['success' => ['Organization successfully updated']] + ], 201); + } catch (\Exception $e) { + logger($e->getMessage() . " at " . $e->getFile() . ":" . $e->getLine()); + // Handle any other exception that may occur + return response()->json([ + 'success' => false, + 'errors' => ['server' => ['Unable to update organization. Check logs for more details']] + ], 500); // 500 Internal Server Error for any other errors + } + } + /** * Submit request to destroy organization to Ringotel * @@ -560,15 +591,6 @@ public function destroyOrganization(Request $request, Domain $domain) } } - - /** - * Submit request to update organization to Ringotel - * - * @return \Illuminate\Http\Response - */ - public function updateOrganization(Request $request) {} - - /** * Submit API request to Ringotel to create a new connection * @@ -582,7 +604,7 @@ public function createConnection(StoreRingotelConnectionRequest $request, Ringot $inputs = $request->validated(); try { - // Send API request to create organization + // Send API request to create connection $connection = $this->ringotelApiService->createConnection($inputs); // Return a JSON response indicating success @@ -615,7 +637,7 @@ public function destroyConnection(RingotelApiService $ringotelApiService) $this->ringotelApiService = $ringotelApiService; try { - // Send API request to create organization + // Send API request to delete connection $connection = $this->ringotelApiService->deleteConnection(request()->all()); // Return a JSON response indicating success @@ -633,12 +655,33 @@ public function destroyConnection(RingotelApiService $ringotelApiService) } /** - * Submit request to update connection to Ringotel + * Submit API request to update connection * * @return \Illuminate\Http\Response */ - public function updateConnection(Request $request) {} + public function updateConnection(UpdateRingotelConnectionRequest $request, RingotelApiService $ringotelApiService) + { + $this->ringotelApiService = $ringotelApiService; + $inputs = $request->validated(); + + try { + // Send API request to create connection + $connection = $this->ringotelApiService->updateConnection($inputs); + + // Return a JSON response indicating success + return response()->json([ + 'messages' => ['success' => ['Connection updated successfully']] + ], 201); + } catch (\Exception $e) { + logger($e->getMessage() . " at " . $e->getFile() . ":" . $e->getLine()); + // Handle any other exception that may occur + return response()->json([ + 'success' => false, + 'errors' => ['server' => ['Unable to update connection. Check logs for more details']] + ], 500); // 500 Internal Server Error for any other errors + } + } /** * Submit getOrganizations request to Ringotel API diff --git a/app/Http/Requests/UpdateRingotelConnectionRequest.php b/app/Http/Requests/UpdateRingotelConnectionRequest.php new file mode 100644 index 00000000..9d3f8703 --- /dev/null +++ b/app/Http/Requests/UpdateRingotelConnectionRequest.php @@ -0,0 +1,114 @@ + 'present', + 'conn_id' => 'present', + 'connection_name' => 'required|string|max:100', + 'protocol' => 'required|string', + 'domain' => 'required|string', + 'port' => 'nullable|numeric', + 'dont_verify_server_certificate' => 'present', + 'disable_srtp' => 'present', + 'multitenant' => 'present', + 'proxy' => 'nullable|string', + 'g711u_enabled' => 'present', + 'g711a_enabled' => 'present', + 'g729_enabled' => 'present', + 'opus_enabled' => 'present', + 'registration_ttl' => 'required|numeric', + 'max_registrations' => 'required|numeric', + 'app_opus_codec' => 'present', + 'one_push' => 'present', + 'show_call_settings' => 'present', + 'allow_call_recording' => 'present', + 'allow_state_change' => 'present', + 'allow_video_calls' => 'present', + 'allow_internal_chat' => 'present', + 'disable_iphone_recents' => 'present', + 'call_delay' => 'required|numeric', + 'desktop_app_delay' => 'present', + 'pbx_features' => 'present', + 'voicemail_extension' => 'nullable|string', + 'dnd_on_code' => 'nullable|string', + 'dnd_off_code' => 'nullable|string', + 'cf_on_code' => 'nullable|string', + 'cf_off_code' => 'nullable|string', + ]; + } + + + public function prepareForValidation(): void + { + // Check if 'region' is missing or empty and set it to null + if (!$this->has('protocol') || $this->input('protocol') === 'NULL') { + $this->merge(['protocol' => null]); + } + + // if ($this->has('dont_send_user_credentials')) { + // $this->merge([ + // 'dont_send_user_credentials' => $this->dont_send_user_credentials ? 'true' : 'false', + // ]); + // } + + } + + /** + * Sanitize the input field to prevent XSS and remove unwanted characters. + * + * @param string $input + * @return string + */ + protected function sanitizeInput(string $input): string + { + // Trim whitespace + $input = trim($input); + + // Strip HTML tags + $input = strip_tags($input); + + // Escape special characters + $input = htmlspecialchars($input, ENT_QUOTES, 'UTF-8'); + + // Remove any non-ASCII characters if necessary (optional) + $input = preg_replace('/[^\x20-\x7E]/', '', $input); + + return $input; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'voicemail_id' => 'voicemail extension', + 'voicemail_password' => 'voicemail password', + 'greeting_id' => 'extension number', + 'voicemail_mail_to' => 'email address', + 'voicemail_enabled' => 'enabled', + 'voicemail_description' => 'description', + 'voicemail_alternate_greet_id' => 'value', + ]; + } +} diff --git a/app/Http/Requests/UpdateRingotelOrganizationRequest.php b/app/Http/Requests/UpdateRingotelOrganizationRequest.php new file mode 100644 index 00000000..5adb38ec --- /dev/null +++ b/app/Http/Requests/UpdateRingotelOrganizationRequest.php @@ -0,0 +1,88 @@ + 'present', + 'organization_name' => 'required|string|max:100', + 'package' => 'required', + 'dont_send_user_credentials' => 'required', + 'domain_uuid' => 'present', + ]; + } + + + public function prepareForValidation(): void + { + + // Check if 'package' is missing or empty and set it to null + if (!$this->has('package') || $this->input('package') === 'NULL') { + $this->merge(['package' => null]); + } + + if ($this->has('dont_send_user_credentials')) { + $this->merge([ + 'dont_send_user_credentials' => $this->dont_send_user_credentials ? 'true' : 'false', + ]); + } + + } + + /** + * Sanitize the input field to prevent XSS and remove unwanted characters. + * + * @param string $input + * @return string + */ + protected function sanitizeInput(string $input): string + { + // Trim whitespace + $input = trim($input); + + // Strip HTML tags + $input = strip_tags($input); + + // Escape special characters + $input = htmlspecialchars($input, ENT_QUOTES, 'UTF-8'); + + // Remove any non-ASCII characters if necessary (optional) + $input = preg_replace('/[^\x20-\x7E]/', '', $input); + + return $input; + } + + /** + * Get custom attributes for validator errors. + * + * @return array + */ + public function attributes(): array + { + return [ + 'voicemail_id' => 'voicemail extension', + 'voicemail_password' => 'voicemail password', + 'greeting_id' => 'extension number', + 'voicemail_mail_to' => 'email address', + 'voicemail_enabled' => 'enabled', + 'voicemail_description' => 'description', + 'voicemail_alternate_greet_id' => 'value', + ]; + } +} diff --git a/app/Services/RingotelApiService.php b/app/Services/RingotelApiService.php index 14125628..4a80820d 100644 --- a/app/Services/RingotelApiService.php +++ b/app/Services/RingotelApiService.php @@ -53,6 +53,42 @@ public function createOrganization($params) return $response['result']; } + public function updateOrganization($params) + { + // Prepare the payload + $data = [ + 'method' => 'updateOrganization', + 'params' => [ + 'id' => $params['organization_id'], + 'name' => $params['organization_name'], + 'packageid' => (int) $params['package'], + 'params' => [ + 'hidePassInEmail' => $params['dont_send_user_credentials'], + ], + ], + ]; + + $response = Http::ringotel() + ->timeout($this->timeout) + ->withBody(json_encode($data), 'application/json') + ->post('/') + ->throw(function () { + throw new \Exception("Unable to update organization"); + }) + ->json(); + + if (isset($response['error'])) { + throw new \Exception($response['error']['message']); + } + + // Handle empty response + if (!$response) { + return ['success' => true, 'message' => 'Organization updated successfully']; + } + + return $response['result']; + } + public function getOrganization($org_id) { // Prepare the payload @@ -232,7 +268,7 @@ public function createConnection($params) ->withBody(json_encode($data), 'application/json') ->post('/') ->throw(function () { - throw new \Exception("Unable to activate organization"); + throw new \Exception("Unable to create connection"); }) ->json(); @@ -247,6 +283,143 @@ public function createConnection($params) return $response['result']; } + public function updateConnection($params) + { + + // Build codecs array based on enabled flags + $codecs = []; + + if ($params['g711u_enabled']) { + $codecs[] = [ + 'codec' => 'G.711 Ulaw', + 'frame' => 20, + ]; + } + + if ($params['g711a_enabled']) { + $codecs[] = [ + 'codec' => 'G.711 Alaw', + 'frame' => 20, + ]; + } + + if ($params['g729_enabled']) { + $codecs[] = [ + 'codec' => 'G.729', + 'frame' => 20, + ]; + } + + if ($params['opus_enabled']) { + $codecs[] = [ + 'codec' => 'OPUS', + 'frame' => 20, + ]; + } + + // Build data array + $data = array( + 'method' => 'updateBranch', + 'params' => array( + "id" => $params['conn_id'], + "orgid" => $params['org_id'], + 'name' => $params['connection_name'], + 'address' => $params['domain'] . ":" . $params['port'], + 'provision' => array( + 'protocol' => $params['protocol'], + 'noverify' => $params['dont_verify_server_certificate'], + 'nosrtp' => $params['disable_srtp'], + 'multitenant' => $params['multitenant'], + 'norec' => !$params['allow_call_recording'], + // 'internal' => false, + // 'sms' => false, + 'maxregs' => (int) $params['max_registrations'], + // 'private' => false, + 'dtmfmode' => 'rfc2833', + 'regexpires' => (int) $params['registration_ttl'], + 'proxy' => array( + 'paddr' => $params['proxy'], + 'pauth' => '', + 'ppass' => '', + ), + 'httpsproxy' => array( + 'address' => '', + ), + 'certificate' => '', + 'tones' => array( + 'Ringback2' => 'Ringback 1', + 'Progress' => 'Progress 1', + 'Ringback' => 'United States', + ), + '1push' => $params['one_push'], + 'noptions' => !$params['show_call_settings'], + 'norecents' => $params['disable_iphone_recents'], + 'novideo' => !$params['allow_video_calls'], + 'nostates' => !$params['allow_state_change'], + 'nochats' => !$params['allow_internal_chat'], + 'calldelay' => $params['call_delay'], + 'pcdelay' => $params['desktop_app_delay'], + 'features' => $params['pbx_features'] ? 'pbx' : '', + "speeddial" => array( + [ + 'number' => $params['voicemail_extension'], + 'title' => 'Voicemail' + ] + ), + 'vmail' => [ + 'ext' => $params['voicemail_extension'], + 'name' => 'Voicemail', + 'mess' => 'You have a new message', + 'off' => '', + 'on' => '', + 'spref' => '' + ], + 'dnd' => [ + 'off' => $params['dnd_on_code'], + 'on' => $params['dnd_off_code'] + ], + 'forwarding' => [ + 'cfuon' => '', + 'cfboff' => '', + 'cfon' => $params['cf_on_code'], + 'cfbon' => '', + 'cfuoff' => '', + 'cfoff' => $params['cf_off_code'] + ], + + 'codecs' => $codecs, + 'app' => array( + 'g711' => !$params['app_opus_codec'], + + ), + + ) + ) + ); + + // logger($data); + + $response = Http::ringotel() + ->timeout($this->timeout) + ->withBody(json_encode($data), 'application/json') + ->post('/') + ->throw(function () { + throw new \Exception("Unable to update connection"); + }) + ->json(); + + if (isset($response['error'])) { + throw new \Exception($response['error']['message']); + } + + // Handle empty response + if (!$response) { + return ['success' => true, 'message' => 'Connection updated successfully']; + } + + return $response['result']; + } + public function deleteConnection($params) { diff --git a/resources/js/Pages/RingotelAppSettings.vue b/resources/js/Pages/RingotelAppSettings.vue index a7bb2377..d9ce3fd4 100644 --- a/resources/js/Pages/RingotelAppSettings.vue +++ b/resources/js/Pages/RingotelAppSettings.vue @@ -167,8 +167,8 @@ @@ -307,7 +307,7 @@ const handleUpdateRequest = (form) => { updateFormSubmiting.value = true; formErrors.value = null; - axios.put(form.update_route, form) + axios.put(props.routes.update_organization, form) .then((response) => { updateFormSubmiting.value = false; showNotification('success', response.data.messages); @@ -442,7 +442,7 @@ const getItemOptions = (itemUuid = null) => { .then((response) => { loadingModal.value = false; itemOptions.value = response.data; - console.log(itemOptions.value); + // console.log(itemOptions.value); }).catch((error) => { handleModalClose(); diff --git a/resources/js/Pages/components/forms/CreateRingotelOrgForm.vue b/resources/js/Pages/components/forms/CreateRingotelOrgForm.vue index f5236117..be3008e6 100644 --- a/resources/js/Pages/components/forms/CreateRingotelOrgForm.vue +++ b/resources/js/Pages/components/forms/CreateRingotelOrgForm.vue @@ -74,8 +74,7 @@
{{ errors.package[0] }}
-

The selected package defines the - available features.

+

Choose a package to set available features.

@@ -120,6 +119,7 @@ @@ -153,6 +153,15 @@ @cancel="handleModalClose" />
+ + + + @@ -178,6 +187,7 @@ import { BuildingOfficeIcon } from '@heroicons/vue/24/outline'; import RingotelConnections from "../general/RingotelConnections.vue"; import AddEditItemModal from "../modal/AddEditItemModal.vue"; import CreateRingotelConnectionForm from "../forms/CreateRingotelConnectionForm.vue"; +import UpdateRingotelConnectionForm from "../forms/UpdateRingotelConnectionForm.vue"; const ringotelConnectionFormSubmiting = ref(null); const loadingModal = ref(false); @@ -206,7 +216,8 @@ const setActiveTab = (tabSlug) => { const showConnectionModal = ref(false); const showConnectionDeletingStatus = ref(false); - +const selectedConnection = ref(null); +const showEditConnectionModal = ref(false); // Map icon names to their respective components const iconComponents = { @@ -288,6 +299,50 @@ const handleCreateConnectionRequest = (form) => { }; +const handleUpdateConnectionRequest = (form) => { + ringotelConnectionFormSubmiting.value = true; + emits('clear-errors'); + + axios.post(props.options.routes.create_connection, form) + .then((response) => { + ringotelConnectionFormSubmiting.value = false; + emits('success', response.data.messages); + + // Add the new connection to the connections array + connections.value.push({ + org_id: response.data.org_id, + conn_id: response.data.conn_id, + connection_name: response.data.connection_name, + domain: response.data.domain + }); + + handleModalClose(); + // handleClearSelection(); + }).catch((error) => { + ringotelConnectionFormSubmiting.value = false; + // handleClearSelection(); + // handleFormErrorResponse(error); + emits('error', error); // Emit the event with error + }); + +}; + +const handleEditConnection = (connection) => { + emits('clear-errors'); + // Find the matching connection from props.options.connections + const matchedConnection = props.options.connections.find( + (conn) => conn.id === connection.conn_id + ); + + if (matchedConnection) { + selectedConnection.value = matchedConnection; + showEditConnectionModal.value = true; + // console.log(selectedConnection.value); + } else { + emits('error', { request: "Matching connection not found" }); + } +} + const handleDeleteConnectionRequest = (connection) => { showConnectionDeletingStatus.value = true; // emits('clear-errors'); @@ -312,6 +367,7 @@ const handleDeleteConnectionRequest = (connection) => { const handleModalClose = () => { showConnectionModal.value = false; + showEditConnectionModal.value = false; } diff --git a/resources/js/Pages/components/forms/UpdateRingotelConnectionForm.vue b/resources/js/Pages/components/forms/UpdateRingotelConnectionForm.vue new file mode 100644 index 00000000..4eb47d6b --- /dev/null +++ b/resources/js/Pages/components/forms/UpdateRingotelConnectionForm.vue @@ -0,0 +1,581 @@ + + + \ No newline at end of file diff --git a/resources/js/Pages/components/forms/UpdateRingotelOrgForm.vue b/resources/js/Pages/components/forms/UpdateRingotelOrgForm.vue index e22f92f8..3890e78c 100644 --- a/resources/js/Pages/components/forms/UpdateRingotelOrgForm.vue +++ b/resources/js/Pages/components/forms/UpdateRingotelOrgForm.vue @@ -45,7 +45,7 @@ + id="organization_domain" class="mt-2" :error="!!errors?.organization_domain" disabled/>
{{ errors.organization_domain[0] }}
@@ -56,12 +56,12 @@ + @update:model-value="handleUpdateRegionField" disabled/>
{{ errors.region[0] }}
-

Choose the region closest to your users - location. You won't be able to change it later.

+ @@ -74,8 +74,7 @@
{{ errors.package[0] }}
-

The selected package defines the - available features.

+

Choose a package to set available features.

@@ -98,7 +97,7 @@ class="inline-flex justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 sm:col-start-2" ref="saveButtonRef" :disabled="isSubmitting"> - Next + Save @@ -120,6 +119,7 @@ @@ -131,7 +131,7 @@ class="inline-flex justify-center rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 sm:col-start-2 disabled:opacity-50 disabled:cursor-not-allowed disabled:bg-indigo-300 disabled:text-indigo-500" :disabled="connections.length == 0"> - Finish + Close @@ -153,6 +153,15 @@ @cancel="handleModalClose" /> + + + + @@ -178,6 +187,7 @@ import { BuildingOfficeIcon } from '@heroicons/vue/24/outline'; import RingotelConnections from "../general/RingotelConnections.vue"; import AddEditItemModal from "../modal/AddEditItemModal.vue"; import CreateRingotelConnectionForm from "../forms/CreateRingotelConnectionForm.vue"; +import UpdateRingotelConnectionForm from "../forms/UpdateRingotelConnectionForm.vue"; const ringotelConnectionFormSubmiting = ref(null); const loadingModal = ref(false); @@ -201,12 +211,13 @@ watch( ); const setActiveTab = (tabSlug) => { - // activeTab.value = tabSlug; + activeTab.value = tabSlug; }; const showConnectionModal = ref(false); const showConnectionDeletingStatus = ref(false); - +const selectedConnection = ref(null); +const showEditConnectionModal = ref(false); // Map icon names to their respective components const iconComponents = { @@ -214,20 +225,32 @@ const iconComponents = { 'BuildingOfficeIcon': BuildingOfficeIcon, }; -const connections = ref([...props.options.connections]); +const connections = ref( + props.options.connections.map((conn) => ({ + org_id: conn.accountId, + conn_id: conn.id, + connection_name: conn.name, + domain: conn.address + })) +); // Watch for changes in props.options.connections and update the local variable watch( () => props.options.connections, (newConnections) => { - connections.value = [...newConnections]; + connections.value = newConnections.map((conn) => ({ + org_id: conn.accountId, + conn_id: conn.id, + connection_name: conn.name, + domain: conn.address + })); } ); - const page = usePage(); const form = reactive({ + organization_id: props.options.organization.id, organization_name: props.options.organization.name, organization_domain: props.options.organization.domain, region: props.options.organization.region, @@ -260,6 +283,7 @@ const handleAddConnection = (selected) => { showConnectionModal.value = true; } + const handleCreateConnectionRequest = (form) => { ringotelConnectionFormSubmiting.value = true; emits('clear-errors'); @@ -288,6 +312,42 @@ const handleCreateConnectionRequest = (form) => { }; +const handleUpdateConnectionRequest = (form) => { + ringotelConnectionFormSubmiting.value = true; + emits('clear-errors'); + + axios.put(props.options.routes.update_connection, form) + .then((response) => { + ringotelConnectionFormSubmiting.value = false; + emits('success', response.data.messages); + + handleModalClose(); + // handleClearSelection(); + }).catch((error) => { + ringotelConnectionFormSubmiting.value = false; + // handleClearSelection(); + // handleFormErrorResponse(error); + emits('error', error); // Emit the event with error + }); + +}; + +const handleEditConnection = (connection) => { + emits('clear-errors'); + // Find the matching connection from props.options.connections + const matchedConnection = props.options.connections.find( + (conn) => conn.id === connection.conn_id + ); + + if (matchedConnection) { + selectedConnection.value = matchedConnection; + showEditConnectionModal.value = true; + // console.log(selectedConnection.value); + } else { + emits('error', { request: "Matching connection not found" }); + } +} + const handleDeleteConnectionRequest = (connection) => { showConnectionDeletingStatus.value = true; // emits('clear-errors'); @@ -301,7 +361,6 @@ const handleDeleteConnectionRequest = (connection) => { (conn) => conn.conn_id !== connection.conn_id ); connections.value = updatedConnections; - console.log(connections.value); }).catch((error) => { showConnectionDeletingStatus.value = false; @@ -312,6 +371,7 @@ const handleDeleteConnectionRequest = (connection) => { const handleModalClose = () => { showConnectionModal.value = false; + showEditConnectionModal.value = false; } diff --git a/resources/js/Pages/components/general/RingotelConnections.vue b/resources/js/Pages/components/general/RingotelConnections.vue index 0325da1f..175465d1 100644 --- a/resources/js/Pages/components/general/RingotelConnections.vue +++ b/resources/js/Pages/components/general/RingotelConnections.vue @@ -51,10 +51,10 @@
- + @@ -107,7 +107,7 @@ const props = defineProps({ }); -const emit = defineEmits(['update:model-value', 'add-connection', 'edit', 'delete-connection']) +const emit = defineEmits(['update:model-value', 'add-connection', 'edit-connection', 'delete-connection']) // Create a local reactive copy of the modelValue const connections = ref([...props.modelValue]); @@ -126,8 +126,8 @@ watch( const handleAddConnection = () => emit('add-connection'); -const handleEdit = (index) => { - emit('edit', index); // Emit the edit event with the index +const handleEdit = (connection) => { + emit('edit-connection', connection); // Emit the edit event with }; const handleDelete = (connection) => { diff --git a/routes/web.php b/routes/web.php index 390591f2..4701b888 100644 --- a/routes/web.php +++ b/routes/web.php @@ -219,12 +219,14 @@ Route::resource('apps', AppsController::class); Route::post('apps/item-options', [AppsController::class, 'getItemOptions'])->name('apps.item.options'); Route::post('/apps/organization/create', [AppsController::class, 'createOrganization'])->name('apps.organization.create'); + Route::put('/apps/organization/update', [AppsController::class, 'updateOrganization'])->name('apps.organization.update'); Route::delete('/apps/organization/{domain}', [AppsController::class, 'destroyOrganization'])->name('appsDestroyOrganization'); // Route::get('/apps/organization/', [AppsController::class, 'getOrganizations'])->name('appsGetOrganizations'); Route::post('/apps/organization/sync', [AppsController::class, 'syncOrganizations'])->name('appsSyncOrganizations'); Route::post('/apps/users/{extension}', [AppsController::class, 'mobileAppUserSettings'])->name('mobileAppUserSettings'); //Route::get('/apps/organization/update', [AppsController::class, 'updateOrganization']) ->name('appsUpdateOrganization'); Route::post('/apps/connection/create', [AppsController::class, 'createConnection'])->name('apps.connection.create'); + Route::put('/apps/connection/update', [AppsController::class, 'updateConnection'])->name('apps.connection.update'); Route::post('/apps/connection/delete', [AppsController::class, 'destroyConnection'])->name('apps.connection.destroy'); Route::get('/apps/connection/update', [AppsController::class, 'updateConnection'])->name('appsUpdateConnection'); Route::post('/apps/user/create', [AppsController::class, 'createUser'])->name('appsCreateUser'); From 97fc99f06fceebc52bfd35b26c321dd49b7c4740 Mon Sep 17 00:00:00 2001 From: nemerald-voip Date: Sat, 28 Dec 2024 00:04:54 -0800 Subject: [PATCH 18/19] added deactivation functions --- app/Http/Controllers/AppsController.php | 146 ++++++------------ app/Services/RingotelApiService.php | 45 ++++++ resources/js/Pages/RingotelAppSettings.vue | 84 ++++------ .../forms/UpdateRingotelOrgForm.vue | 9 +- routes/web.php | 2 +- 5 files changed, 128 insertions(+), 158 deletions(-) diff --git a/app/Http/Controllers/AppsController.php b/app/Http/Controllers/AppsController.php index 61409b8e..d16d7a5d 100644 --- a/app/Http/Controllers/AppsController.php +++ b/app/Http/Controllers/AppsController.php @@ -56,6 +56,7 @@ public function index() 'current_page' => route('apps.index'), 'create_organization' => route('apps.organization.create'), 'update_organization' => route('apps.organization.update'), + 'destroy_organization' => route('apps.organization.destroy'), 'item_options' => route('apps.item.options'), ] ] @@ -480,116 +481,61 @@ public function UpdateOrganization(UpdateRingotelOrganizationRequest $request, R * * @return \Illuminate\Http\JsonResponse */ - public function destroyOrganization(Request $request, Domain $domain) + public function destroyOrganization(RingotelApiService $ringotelApiService) { - - // Get Org ID from database - $org_id = appsGetOrganizationDetails($domain->domain_uuid); - - //Get all connections - $response = appsGetConnections($org_id); - - if (isset($response['error'])) { - return response()->json([ - 'status' => 401, - 'error' => [ - 'message' => $response['error']['message'], - ], - 'domain' => $domain->domain_name, - ]); - } - - //Delete all connections - foreach ($response['result'] as $conn) { - $response = appsDeleteConnection($org_id, $conn['id']); - if (isset($response['error'])) { + $this->ringotelApiService = $ringotelApiService; + + try { + // Get Org ID from database + $domain_uuid = request('domain_uuid'); + $org_id = $this->ringotelApiService->getOrgIdByDomainUuid($domain_uuid); + + if (!$org_id) { return response()->json([ - 'status' => 401, - 'error' => [ - 'message' => $response['error']['message'], - ], - 'domain' => $domain->domain_name, + 'success' => false, + 'errors' => ['server' => ['Organization ID not found for the given domain.']] + ], 404); // 404 Not Found + } + + // Retrieve all connections for the organization + $connections = $this->ringotelApiService->getConnections($org_id); + + // Delete each connection + foreach ($connections as $connection) { + $this->ringotelApiService->deleteConnection([ + 'conn_id' => $connection->id, + 'org_id' => $org_id, ]); } - } - - // Delete organization - $response = appsDeleteOrganization($org_id); - if (isset($response['error'])) { - return response()->json([ - 'status' => 401, - 'error' => [ - 'message' => $response['error']['message'], - ], - 'domain' => $domain->domain_name, - ]); - } - //Detele records from database - $appOrgID = DomainSettings::where('domain_uuid', $domain->domain_uuid) - ->where('domain_setting_category', 'app shell') - ->where('domain_setting_subcategory', 'org_id') - ->first(); - - Log::info($appOrgID); - - $appOrgID->delete(); - - - return response()->json([ - 'org_details' => $org_id, - 'connections' => $response, - // 'organization_domain' => $request->organization_domain, - // 'organization_region' => $request->organization_region, - // 'org_id' => $response['result']['id'], - 'message' => 'Success', - ]); - - - - - // !!!!! TODO: The code below is unreachable, do we need it ? !!!!! - // If successful store Org ID and return success status - if (isset($response['result'])) { - - // Add recieved OrgID to the request and store it in database - $request->merge(['org_id' => $response['result']['id']]); - - if (!appsStoreOrganizationDetails($request)) { + + // Delete the organization + $deleteResponse = $this->ringotelApiService->deleteOrganization($org_id); + + if ($deleteResponse) { + // Remove local references from the database + DomainSettings::where('domain_uuid', $domain_uuid) + ->where('domain_setting_category', 'app shell') + ->where('domain_setting_subcategory', 'org_id') + ->delete(); + return response()->json([ - 'organization_name' => $request->organization_name, - 'organization_domain' => $request->organization_domain, - 'organization_region' => $request->organization_region, - 'org_id' => $response['result']['id'], - 'message' => 'Organization was created succesfully, but unable to store Org ID in database', - ]); + 'messages' => ['success' => ['Organization and its connections were successfully deleted.']] + ], 200); // 200 OK } - + return response()->json([ - 'organization_name' => $request->organization_name, - 'organization_domain' => $request->organization_domain, - 'organization_region' => $request->organization_region, - 'org_id' => $response['result']['id'], - 'message' => 'Organization created succesfully', - ]); - // Otherwise return failed status - } elseif (isset($response['error'])) { - return response()->json([ - 'error' => 401, - 'organization_name' => $request->organization_name, - 'organization_domain' => $request->organization_domain, - 'organization_region' => $request->organization_region, - 'message' => $response['error']['message'] - ]); - } else { + 'success' => false, + 'errors' => ['server' => ['Failed to delete the organization.']] + ], 500); // 500 Internal Server Error + + } catch (\Exception $e) { return response()->json([ - 'error' => 401, - 'organization_name' => $request->organization_name, - 'organization_domain' => $request->organization_domain, - 'organization_region' => $request->organization_region, - 'message' => 'Unknown error' - ]); + 'success' => false, + 'errors' => ['server' => [$e->getMessage()]] + ], 500); // 500 Internal Server Error } } + /** * Submit API request to Ringotel to create a new connection diff --git a/app/Services/RingotelApiService.php b/app/Services/RingotelApiService.php index 4a80820d..1e99ac59 100644 --- a/app/Services/RingotelApiService.php +++ b/app/Services/RingotelApiService.php @@ -2,6 +2,7 @@ namespace App\Services; +use App\Models\DomainSettings; use App\DTO\RingotelConnectionDTO; use Illuminate\Support\Facades\DB; use App\DTO\RingotelOrganizationDTO; @@ -120,6 +121,40 @@ public function getOrganization($org_id) return RingotelOrganizationDTO::fromArray($response['result']); } + public function deleteOrganization($org_id) + { + // Prepare the payload + $data = [ + 'method' => 'deleteOrganization', + 'params' => [ + 'id' => $org_id, + ], + ]; + + // Send the request + $response = Http::ringotel() // Ensure `ringotel` is configured in the HTTP client + ->timeout($this->timeout) + ->withBody(json_encode($data), 'application/json') + ->post('/') + ->throw(function ($response) { + throw new \Exception("Failed to delete organization: {$response->body()}"); + }) + ->json(); + + // Check for errors in the response + if (isset($response['error'])) { + throw new \Exception($response['error']['message']); + } + + // Handle empty response + if (!$response) { + return ['success' => true, 'message' => 'Organization and its connections were successfully deleted.']; + } + + return $response['result']; + } + + public function getOrganizations() { $data = array( @@ -538,4 +573,14 @@ public function matchLocalDomains($organizations) return $orgArray; } + + public function getOrgIdByDomainUuid($domain_uuid) + { + return DomainSettings::where([ + ['domain_uuid', '=', $domain_uuid], + ['domain_setting_category', '=', 'app shell'], + ['domain_setting_subcategory', '=', 'org_id'], + ['domain_setting_enabled', '=', true], + ])->value('domain_setting_value'); + } } diff --git a/resources/js/Pages/RingotelAppSettings.vue b/resources/js/Pages/RingotelAppSettings.vue index d9ce3fd4..054f822a 100644 --- a/resources/js/Pages/RingotelAppSettings.vue +++ b/resources/js/Pages/RingotelAppSettings.vue @@ -119,10 +119,10 @@
-
-
@@ -167,23 +167,15 @@ - - - - - + @@ -201,20 +193,20 @@ import TableColumnHeader from "./components/general/TableColumnHeader.vue"; import TableField from "./components/general/TableField.vue"; import Paginator from "./components/general/Paginator.vue"; import AddEditItemModal from "./components/modal/AddEditItemModal.vue"; -import DeleteConfirmationModal from "./components/modal/DeleteConfirmationModal.vue"; +import ConfirmationModal from "./components/modal/ConfirmationModal.vue"; import Loading from "./components/general/Loading.vue"; import { registerLicense } from '@syncfusion/ej2-base'; import { MagnifyingGlassIcon, TrashIcon, PencilSquareIcon } from "@heroicons/vue/24/solid"; import { TooltipComponent as EjsTooltip } from "@syncfusion/ej2-vue-popups"; -import BulkUpdateDeviceForm from "./components/forms/BulkUpdateDeviceForm.vue"; import BulkActionButton from "./components/general/BulkActionButton.vue"; import MainLayout from "../Layouts/MainLayout.vue"; import CreateRingotelOrgForm from "./components/forms/CreateRingotelOrgForm.vue"; import UpdateRingotelOrgForm from "./components/forms/UpdateRingotelOrgForm.vue"; import Notification from "./components/notifications/Notification.vue"; import Badge from "@generalComponents/Badge.vue"; -import LinkOffIcon from "@icons/LinkOffIcon.vue"; import { PowerIcon } from "@heroicons/vue/24/outline"; +import { XCircleIcon } from "@heroicons/vue/24/outline"; + const page = usePage() @@ -226,11 +218,11 @@ const selectPageItems = ref(false); const showActivateModal = ref(false); const showEditModal = ref(false); const bulkUpdateModalTrigger = ref(false); -const confirmationModalTrigger = ref(false); -const confirmationModalDestroyPath = ref(null); +const showConfirmationModal = ref(false); const activateFormSubmiting = ref(null); const activationActiveTab = ref('organization'); const updateFormSubmiting = ref(null); +const showDeactivateSpinner = ref(null); const confirmDeleteAction = ref(null); const bulkUpdateFormSubmiting = ref(null); const formErrors = ref(null); @@ -269,6 +261,7 @@ onMounted(() => { }); const handleActivateButtonClick = (itemUuid) => { + activationActiveTab.value = 'organization'; showActivateModal.value = true formErrors.value = null; loadingModal.value = true @@ -292,9 +285,7 @@ const handleCreateRequest = (form) => { showNotification('success', response.data.messages); itemOptions.value.orgId = response.data.org_id; activationActiveTab.value = 'connections'; - // handleSearchButtonClick(); - // handleModalClose(); - // handleClearSelection(); + }).catch((error) => { activateFormSubmiting.value = false; handleClearSelection(); @@ -322,38 +313,31 @@ const handleUpdateRequest = (form) => { }; -const handleSingleItemDeleteRequest = (url) => { - confirmationModalTrigger.value = true; - confirmDeleteAction.value = () => executeSingleDelete(url); +const handleDeactivateButtonClick = (uuid) => { + showConfirmationModal.value = true; + confirmDeleteAction.value = () => executeSingleDelete(uuid); } -const executeSingleDelete = (url) => { - router.delete(url, { - preserveScroll: true, - preserveState: true, - onSuccess: (page) => { - if (page.props.flash.error) { - showNotification('error', page.props.flash.error); - } - if (page.props.flash.message) { - showNotification('success', page.props.flash.message); - } - confirmationModalTrigger.value = false; - confirmationModalDestroyPath.value = null; - }, - onFinish: () => { - confirmationModalTrigger.value = false; - confirmationModalDestroyPath.value = null; - }, - onError: (errors) => { - console.log(errors); - }, - }); +const executeSingleDelete = (uuid) => { + showDeactivateSpinner.value = true; + + axios.post(props.routes.destroy_organization, { domain_uuid: uuid }) + .then((response) => { + showDeactivateSpinner.value = false; + showNotification('success', response.data.messages); + handleSearchButtonClick(); + handleModalClose(); + handleClearSelection(); + }).catch((error) => { + showDeactivateSpinner.value = false; + handleClearSelection(); + handleFormErrorResponse(error); + }); } const handleBulkActionRequest = (action) => { if (action === 'bulk_delete') { - confirmationModalTrigger.value = true; + showConfirmationModal.value = true; confirmDeleteAction.value = () => executeBulkDelete(); } if (action === 'bulk_update') { @@ -516,7 +500,7 @@ const handleClearSelection = () => { const handleModalClose = () => { showActivateModal.value = false; showEditModal.value = false, - confirmationModalTrigger.value = false; + showConfirmationModal.value = false; bulkUpdateModalTrigger.value = false; } diff --git a/resources/js/Pages/components/forms/UpdateRingotelOrgForm.vue b/resources/js/Pages/components/forms/UpdateRingotelOrgForm.vue index 3890e78c..09947edf 100644 --- a/resources/js/Pages/components/forms/UpdateRingotelOrgForm.vue +++ b/resources/js/Pages/components/forms/UpdateRingotelOrgForm.vue @@ -260,7 +260,7 @@ const form = reactive({ _token: page.props.csrf_token, }) -const emits = defineEmits(['submit', 'cancel', 'error', 'success', 'clear-errors']); +const emits = defineEmits(['submit', 'cancel', 'error', 'success', 'clear-errors', 'refresh-data']); const submitForm = () => { emits('submit', form); // Emit the event with the form data @@ -302,11 +302,8 @@ const handleCreateConnectionRequest = (form) => { }); handleModalClose(); - // handleClearSelection(); }).catch((error) => { ringotelConnectionFormSubmiting.value = false; - // handleClearSelection(); - // handleFormErrorResponse(error); emits('error', error); // Emit the event with error }); @@ -320,13 +317,11 @@ const handleUpdateConnectionRequest = (form) => { .then((response) => { ringotelConnectionFormSubmiting.value = false; emits('success', response.data.messages); + emits('refresh-data',props.options.model.domain_uuid); handleModalClose(); - // handleClearSelection(); }).catch((error) => { ringotelConnectionFormSubmiting.value = false; - // handleClearSelection(); - // handleFormErrorResponse(error); emits('error', error); // Emit the event with error }); diff --git a/routes/web.php b/routes/web.php index 4701b888..6ba5fda3 100644 --- a/routes/web.php +++ b/routes/web.php @@ -220,7 +220,7 @@ Route::post('apps/item-options', [AppsController::class, 'getItemOptions'])->name('apps.item.options'); Route::post('/apps/organization/create', [AppsController::class, 'createOrganization'])->name('apps.organization.create'); Route::put('/apps/organization/update', [AppsController::class, 'updateOrganization'])->name('apps.organization.update'); - Route::delete('/apps/organization/{domain}', [AppsController::class, 'destroyOrganization'])->name('appsDestroyOrganization'); + Route::post('/apps/organization/destroy', [AppsController::class, 'destroyOrganization'])->name('apps.organization.destroy'); // Route::get('/apps/organization/', [AppsController::class, 'getOrganizations'])->name('appsGetOrganizations'); Route::post('/apps/organization/sync', [AppsController::class, 'syncOrganizations'])->name('appsSyncOrganizations'); Route::post('/apps/users/{extension}', [AppsController::class, 'mobileAppUserSettings'])->name('mobileAppUserSettings'); From cc819cdce5b4db2164e896e7f3ad048d29824ba9 Mon Sep 17 00:00:00 2001 From: nemerald-voip Date: Sat, 28 Dec 2024 00:09:54 -0800 Subject: [PATCH 19/19] version bump --- app/Console/Commands/UpdateApp.php | 4 ++-- .../Commands/Updates/{Update0916.php => Update0917.php} | 4 ++-- config/version.php | 2 +- database/seeders/RecommendedSettingsSeeder.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) rename app/Console/Commands/Updates/{Update0916.php => Update0917.php} (99%) diff --git a/app/Console/Commands/UpdateApp.php b/app/Console/Commands/UpdateApp.php index 074c6b2a..4841cd3e 100644 --- a/app/Console/Commands/UpdateApp.php +++ b/app/Console/Commands/UpdateApp.php @@ -6,7 +6,7 @@ use App\Services\GitHubApiService; use Symfony\Component\Process\Process; use App\Console\Commands\Updates\Update097; -use App\Console\Commands\Updates\Update0916; +use App\Console\Commands\Updates\Update0917; class UpdateApp extends Command @@ -49,7 +49,7 @@ public function handle() $updateSteps = [ '0.9.7' => Update097::class, '0.9.11' => Update097::class, - '0.9.16' => Update0916::class, + '0.9.17' => Update0917::class, // Add more versions as needed ]; diff --git a/app/Console/Commands/Updates/Update0916.php b/app/Console/Commands/Updates/Update0917.php similarity index 99% rename from app/Console/Commands/Updates/Update0916.php rename to app/Console/Commands/Updates/Update0917.php index 3f9c776a..3c88bda0 100644 --- a/app/Console/Commands/Updates/Update0916.php +++ b/app/Console/Commands/Updates/Update0917.php @@ -5,7 +5,7 @@ use App\Models\DefaultSettings; use Illuminate\Support\Str; -class Update0916 +class Update0917 { /** * Apply the 0.9.16 update steps. @@ -69,7 +69,7 @@ public function apply() 'subcategory' => 'mobile_app_conn_protocol', 'type' => 'text', 'value' => 'sip', - 'description' => 'sip or tcp or sips', + 'description' => 'sip or sip-tcp or sips or DNS-NAPTR', ], [ 'category' => 'mobile_apps', diff --git a/config/version.php b/config/version.php index 03e4d2e3..1c17fe9a 100644 --- a/config/version.php +++ b/config/version.php @@ -2,6 +2,6 @@ return [ - 'release' => '0.9.16', + 'release' => '0.9.17', ]; \ No newline at end of file diff --git a/database/seeders/RecommendedSettingsSeeder.php b/database/seeders/RecommendedSettingsSeeder.php index 99c4f687..26adc8aa 100644 --- a/database/seeders/RecommendedSettingsSeeder.php +++ b/database/seeders/RecommendedSettingsSeeder.php @@ -124,7 +124,7 @@ private function createDefaultSettings() 'default_setting_name' => 'text', 'default_setting_value' => 'sip', 'default_setting_enabled' => true, - 'default_setting_description' => "Options: sip or tcp or sips", + 'default_setting_description' => "Options: sip or sip-tcp or sips or DNS-NAPTR", ], [ 'default_setting_category' => 'mobile_apps',